From c52e40d32662531714f17ecd498772bc5938aec4 Mon Sep 17 00:00:00 2001 From: pedronvasconcelos Date: Sat, 24 Feb 2024 21:33:12 -0300 Subject: [PATCH 1/3] add: Mapping + Like a spixer. --- Spix.sln | 5 ++ sql/create_db.sql | 37 ++++++++ src/Spix.Api/Controllers/SpixerController.cs | 14 +++- src/Spix.Api/Spix.Api.http | 6 -- .../Create/CreateSpixerCommandHandler.cs | 8 +- .../Spixers/Like/LikeASpixerCommand.cs | 14 ++++ .../Spixers/Like/LikeASpixerCommandHandler.cs | 57 +++++++++++++ .../Spixers/Like/LikeASpixerResponse.cs | 17 ++++ src/Spix.Domain/Core/DomainEvent.cs | 2 +- src/Spix.Domain/Core/Entity.cs | 2 +- src/Spix.Domain/Core/IRepository.cs | 2 +- src/Spix.Domain/Likes/SpixerLike.cs | 4 +- src/Spix.Domain/Spixers/ISpixerRepository.cs | 4 +- src/Spix.Domain/Spixers/Media.cs | 4 - .../Rules/ContentMustNotBeEmptyRule.cs | 3 +- src/Spix.Domain/Spixers/Spixer.cs | 27 +++--- src/Spix.Domain/Users/IUserRepository.cs | 9 +- .../Rules/BirthdateCannotBeInFutureRule.cs | 2 +- .../Users/Rules/SpixerConstants.cs | 6 ++ src/Spix.Domain/Users/User.cs | 8 +- src/Spix.Domain/Users/UserLanguage.cs | 5 +- .../Database/Context/SpixDbContext.cs | 17 +++- .../Database/Mapping/SpixerLikeMapping.cs | 42 ++++++++++ .../Database/Mapping/SpixerMapping.cs | 48 ++++++++--- .../Database/Mapping/UserMapping.cs | 84 +++++++++++++++++++ .../Database/Repositories/SpixerRepository.cs | 13 +++ .../Database/Repositories/UserRepositories.cs | 18 ---- .../Database/Repositories/UserRepository.cs | 48 +++++++++++ 28 files changed, 426 insertions(+), 80 deletions(-) create mode 100644 sql/create_db.sql delete mode 100644 src/Spix.Api/Spix.Api.http create mode 100644 src/Spix.Application/Spixers/Like/LikeASpixerCommand.cs create mode 100644 src/Spix.Application/Spixers/Like/LikeASpixerCommandHandler.cs create mode 100644 src/Spix.Application/Spixers/Like/LikeASpixerResponse.cs delete mode 100644 src/Spix.Domain/Spixers/Media.cs create mode 100644 src/Spix.Domain/Users/Rules/SpixerConstants.cs create mode 100644 src/Spix.Infra/Database/Mapping/SpixerLikeMapping.cs create mode 100644 src/Spix.Infra/Database/Mapping/UserMapping.cs delete mode 100644 src/Spix.Infra/Database/Repositories/UserRepositories.cs create mode 100644 src/Spix.Infra/Database/Repositories/UserRepository.cs diff --git a/Spix.sln b/Spix.sln index cddff69..3331955 100644 --- a/Spix.sln +++ b/Spix.sln @@ -17,6 +17,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spix.Infra", "src\Spix.Infr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spix.Api", "src\Spix.Api\Spix.Api.csproj", "{4A950028-5995-4DF7-8949-9CC6DC7278D7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sql", "sql", "{5A6EDF2F-C166-4D24-9103-566E9EC58866}" + ProjectSection(SolutionItems) = preProject + sql\create_db.sql = sql\create_db.sql + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/sql/create_db.sql b/sql/create_db.sql new file mode 100644 index 0000000..f9b0580 --- /dev/null +++ b/sql/create_db.sql @@ -0,0 +1,37 @@ +CREATE TABLE users ( + user_id UUID PRIMARY KEY, + username VARCHAR(50) NOT NULL, + first_name VARCHAR(50), + last_name VARCHAR(50), + birth_date DATE, + country VARCHAR(50), + city VARCHAR(50), + user_language INT, + email VARCHAR(255) NOT NULL, + biography TEXT, + email_is_verified BOOLEAN, + is_active BOOLEAN, + user_location TEXT, + website VARCHAR(255), + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE spixers ( + spider_id UUID PRIMARY KEY, + content VARCHAR(280), + likes_count INT DEFAULT 0, + created_at timestamp with time zone, + user_id UUID, + active BOOLEAN, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE +); + +CREATE TABLE spixer_likes ( + like_id UUID PRIMARY KEY, + spixer_id UUID, + user_id UUID, + created_at timestamp WITH TIME ZONE, + FOREIGN KEY (spixer_id) REFERENCES spixers(spider_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE +); diff --git a/src/Spix.Api/Controllers/SpixerController.cs b/src/Spix.Api/Controllers/SpixerController.cs index 91ade3a..cc3f94a 100644 --- a/src/Spix.Api/Controllers/SpixerController.cs +++ b/src/Spix.Api/Controllers/SpixerController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Spix.Application.Spixers.Create; +using Spix.Application.Spixers.Like; namespace Spix.Api.Controllers { @@ -16,11 +17,20 @@ public SpixerController(IMediator mediator) _mediator = mediator; } - [HttpPost] + [HttpPost] + [Route("[action]")] public async Task CreateSpixer([FromBody] CreateSpixerCommand command) { var response = await _mediator.Send(command); return Ok(response); - } + } + + [HttpPost] + [Route("[action]")] + public async Task LikeSpixer([FromBody] LikeASpixerCommand command) + { + var response = await _mediator.Send(command); + return Ok(response); + } } } diff --git a/src/Spix.Api/Spix.Api.http b/src/Spix.Api/Spix.Api.http deleted file mode 100644 index 6a80582..0000000 --- a/src/Spix.Api/Spix.Api.http +++ /dev/null @@ -1,6 +0,0 @@ -@Spix.Api_HostAddress = http://localhost:5172 - -GET {{Spix.Api_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/src/Spix.Application/Spixers/Create/CreateSpixerCommandHandler.cs b/src/Spix.Application/Spixers/Create/CreateSpixerCommandHandler.cs index 0f4b3e8..e728e1f 100644 --- a/src/Spix.Application/Spixers/Create/CreateSpixerCommandHandler.cs +++ b/src/Spix.Application/Spixers/Create/CreateSpixerCommandHandler.cs @@ -18,16 +18,16 @@ public CreateSpixerCommandHandler(ISpixerRepository spixerRepository, IUserRepos public async Task> Handle(CreateSpixerCommand request, CancellationToken cancellationToken) { - /* var user = await _userRepository.GetAsync(request.UserId); + var user = await _userRepository.GetByIdAsync(request.UserId); if (user == null) { return ResultBaseFactory.Failure("User not found"); } - */ - var spixer = new Spixer(request.Content, request.UserId); + + var spixer = new Spixer(request.Content, user.Id); var addedSpixer = await _spixerRepository.AddAsync(spixer); - if (await _spixerRepository.UnitOfWork.CommitAsync(cancellationToken)) + if (!await _spixerRepository.UnitOfWork.CommitAsync(cancellationToken)) { return ResultBaseFactory.Failure("Error adding spixer"); } diff --git a/src/Spix.Application/Spixers/Like/LikeASpixerCommand.cs b/src/Spix.Application/Spixers/Like/LikeASpixerCommand.cs new file mode 100644 index 0000000..6faa30c --- /dev/null +++ b/src/Spix.Application/Spixers/Like/LikeASpixerCommand.cs @@ -0,0 +1,14 @@ +using Spix.Application.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Spix.Application.Spixers.Like; + +public class LikeASpixerCommand : ICommandBase +{ + public Guid UserId { get; set; } + public Guid SpixerId { get; set; } +} diff --git a/src/Spix.Application/Spixers/Like/LikeASpixerCommandHandler.cs b/src/Spix.Application/Spixers/Like/LikeASpixerCommandHandler.cs new file mode 100644 index 0000000..c7c2811 --- /dev/null +++ b/src/Spix.Application/Spixers/Like/LikeASpixerCommandHandler.cs @@ -0,0 +1,57 @@ +using Spix.Application.Core; +using Spix.Domain.Likes; +using Spix.Domain.Spixers; +using Spix.Domain.Users; + +namespace Spix.Application.Spixers.Like; + +public class LikeASpixerCommandHandler : ICommandHandlerBase +{ + private readonly ISpixerRepository _spixerRepository; + private readonly IUserRepository _userRepository; + + public LikeASpixerCommandHandler(ISpixerRepository spixerRepository, IUserRepository userRepository) + { + _spixerRepository = spixerRepository; + _userRepository = userRepository; + } + public async Task> Handle(LikeASpixerCommand request, CancellationToken cancellationToken) + { + + var user = await _userRepository.GetByIdAsync(request.UserId); + if (user == null) + { + return ResultBaseFactory.Failure("User not found"); + } + var spixer = await _spixerRepository.GetByIdAsync(request.SpixerId); + if (spixer == null) + { + return ResultBaseFactory.Failure("Spixer not found"); + } + if (await _spixerRepository.IsSpixerLikedByUserAsync(request.SpixerId, request.UserId)) + { + return ResultBaseFactory.Failure("Spixer already liked"); + } + + var spixerLike = new SpixerLike(spixer.Id, user.Id); + spixer.Like(spixerLike); + + + await _spixerRepository.UpdateAsync(spixer); + await _spixerRepository.AddSpixerLikeAsync(spixerLike); + if (!await _spixerRepository.UnitOfWork.CommitAsync(cancellationToken)) + { + return ResultBaseFactory.Failure("Error liking spixer"); + } + + + + return ResultBaseFactory.Successful( + new LikeASpixerResponse( + spixerLike.Id, + user.Id, + spixer.Id, + spixerLike.CreatedAt), + "Success"); + } +} diff --git a/src/Spix.Application/Spixers/Like/LikeASpixerResponse.cs b/src/Spix.Application/Spixers/Like/LikeASpixerResponse.cs new file mode 100644 index 0000000..3be0c2c --- /dev/null +++ b/src/Spix.Application/Spixers/Like/LikeASpixerResponse.cs @@ -0,0 +1,17 @@ +namespace Spix.Application.Spixers.Like; + +public class LikeASpixerResponse +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public Guid SpixerId { get; set; } + public DateTime CreatedAt { get; set; } + + public LikeASpixerResponse(Guid id, Guid userId, Guid spixerId, DateTime createdAt) + { + Id = id; + UserId = userId; + SpixerId = spixerId; + CreatedAt = createdAt; + } +} diff --git a/src/Spix.Domain/Core/DomainEvent.cs b/src/Spix.Domain/Core/DomainEvent.cs index ef40339..54b33d5 100644 --- a/src/Spix.Domain/Core/DomainEvent.cs +++ b/src/Spix.Domain/Core/DomainEvent.cs @@ -7,6 +7,6 @@ public class DomainEvent public DomainEvent() { - OccurredOn = DateTime.Now; + OccurredOn = DateTime.UtcNow; } } diff --git a/src/Spix.Domain/Core/Entity.cs b/src/Spix.Domain/Core/Entity.cs index ba89853..aa3a176 100644 --- a/src/Spix.Domain/Core/Entity.cs +++ b/src/Spix.Domain/Core/Entity.cs @@ -6,7 +6,7 @@ public abstract class Entity public List _domainEvents = new List(); - + protected void AddDomainEvent(DomainEvent domainEvent) { _domainEvents = _domainEvents ?? new List(); diff --git a/src/Spix.Domain/Core/IRepository.cs b/src/Spix.Domain/Core/IRepository.cs index b969ebf..8451c68 100644 --- a/src/Spix.Domain/Core/IRepository.cs +++ b/src/Spix.Domain/Core/IRepository.cs @@ -6,7 +6,7 @@ namespace Spix.Domain.Core; -public interface IRepository : IDisposable where T : Entity +public interface IRepository : IDisposable where T : IAggregateRoot { Task GetByIdAsync(Guid id); Task AddAsync(T entity); diff --git a/src/Spix.Domain/Likes/SpixerLike.cs b/src/Spix.Domain/Likes/SpixerLike.cs index c56d433..1b2481d 100644 --- a/src/Spix.Domain/Likes/SpixerLike.cs +++ b/src/Spix.Domain/Likes/SpixerLike.cs @@ -10,13 +10,13 @@ public class SpixerLike : Entity public virtual Spixer Spixer { get; private set; } = null!; public Guid UserId { get; private set; } public virtual User User { get; private set; } = null!; - public DateTime CreatedAt { get; private set; } = DateTime.Now; + public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public SpixerLike(Guid spixerId, Guid userId) { SpixerId = spixerId; UserId = userId; - CreatedAt = DateTime.Now; + CreatedAt = DateTime.UtcNow; } private SpixerLike() diff --git a/src/Spix.Domain/Spixers/ISpixerRepository.cs b/src/Spix.Domain/Spixers/ISpixerRepository.cs index e1b04eb..54e25fb 100644 --- a/src/Spix.Domain/Spixers/ISpixerRepository.cs +++ b/src/Spix.Domain/Spixers/ISpixerRepository.cs @@ -1,9 +1,11 @@  using Spix.Domain.Core; +using Spix.Domain.Likes; namespace Spix.Domain.Spixers; public interface ISpixerRepository : IRepository { - + Task IsSpixerLikedByUserAsync(Guid spixerId, Guid userId); + Task AddSpixerLikeAsync(SpixerLike spixerLike); } diff --git a/src/Spix.Domain/Spixers/Media.cs b/src/Spix.Domain/Spixers/Media.cs deleted file mode 100644 index 4c02a3e..0000000 --- a/src/Spix.Domain/Spixers/Media.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Spix.Domain.Core; -namespace Spix.Domain.Spixers; - - \ No newline at end of file diff --git a/src/Spix.Domain/Spixers/Rules/ContentMustNotBeEmptyRule.cs b/src/Spix.Domain/Spixers/Rules/ContentMustNotBeEmptyRule.cs index dd39968..aae5f64 100644 --- a/src/Spix.Domain/Spixers/Rules/ContentMustNotBeEmptyRule.cs +++ b/src/Spix.Domain/Spixers/Rules/ContentMustNotBeEmptyRule.cs @@ -1,5 +1,6 @@  using Spix.Domain.Core; +using Spix.Domain.Users.Rules; namespace Spix.Domain.Spixers.Rules; @@ -19,7 +20,7 @@ public ContentMustNotBeEmptyRule(string content) public class ContentMustNotExceedMaxLengthRule : IBusinessRule { - private const int MaxLength = 280; + private const int MaxLength = SpixerConstants.MaxContentLength; private readonly string _content; public ContentMustNotExceedMaxLengthRule(string content) diff --git a/src/Spix.Domain/Spixers/Spixer.cs b/src/Spix.Domain/Spixers/Spixer.cs index 6117988..2d2a04a 100644 --- a/src/Spix.Domain/Spixers/Spixer.cs +++ b/src/Spix.Domain/Spixers/Spixer.cs @@ -1,4 +1,5 @@ using Spix.Domain.Core; +using Spix.Domain.Likes; using Spix.Domain.Spixers.Rules; using Spix.Domain.Users; @@ -11,27 +12,24 @@ public class Spixer : Entity, IAggregateRoot public Guid UserId { get; private set; } public virtual User User { get; private set; } = null!; - public List LikedByUsers { get; private set; } = new(); - public int LikesCount => LikedByUsers.Count; + public virtual List SpixerLikes { get; private set; } = new(); + public int LikesCount { get; private set; } public bool Active { get; private set; } = true; - public void AddLike(Guid userId) + public void Like(SpixerLike like) { - if (!LikedByUsers.Contains(userId)) - { - LikedByUsers.Add(userId); - } - AddDomainEvent(new SpixerLikedDomainEvent(this.Id, userId)); + + SpixerLikes.Add(like); + LikesCount++; + AddDomainEvent(new SpixerLikedDomainEvent(this.Id, like.UserId)); } - public void RemoveLike(Guid userId) + public void Unlike(SpixerLike like) { - if (LikedByUsers.Contains(userId)) - { - LikedByUsers.Remove(userId); - } + SpixerLikes.Remove(like); + LikesCount--; } @@ -40,6 +38,7 @@ public Spixer(string content, Guid userId) SetContent(content); UserId = userId; Active = true; + CreatedAt = DateTime.UtcNow; } public void DeleteSpixer() @@ -59,3 +58,5 @@ public Spixer() } } + + diff --git a/src/Spix.Domain/Users/IUserRepository.cs b/src/Spix.Domain/Users/IUserRepository.cs index 31bfdf6..30dc3ba 100644 --- a/src/Spix.Domain/Users/IUserRepository.cs +++ b/src/Spix.Domain/Users/IUserRepository.cs @@ -1,7 +1,8 @@ -namespace Spix.Domain.Users; +using Spix.Domain.Core; -public interface IUserRepository +namespace Spix.Domain.Users; + +public interface IUserRepository : IRepository { - public Task AddAsync(User user); - public Task GetAsync(Guid id); + } diff --git a/src/Spix.Domain/Users/Rules/BirthdateCannotBeInFutureRule.cs b/src/Spix.Domain/Users/Rules/BirthdateCannotBeInFutureRule.cs index 08bdcf3..6af81de 100644 --- a/src/Spix.Domain/Users/Rules/BirthdateCannotBeInFutureRule.cs +++ b/src/Spix.Domain/Users/Rules/BirthdateCannotBeInFutureRule.cs @@ -12,5 +12,5 @@ public BirthdateCannotBeInFutureRule(DateTime birthdate) public string Message => "Birthdate cannot be in future"; public bool IsBroken() - => _birthdate.Date > DateTime.Now.Date; + => _birthdate.Date > DateTime.UtcNow.Date; } diff --git a/src/Spix.Domain/Users/Rules/SpixerConstants.cs b/src/Spix.Domain/Users/Rules/SpixerConstants.cs new file mode 100644 index 0000000..a988912 --- /dev/null +++ b/src/Spix.Domain/Users/Rules/SpixerConstants.cs @@ -0,0 +1,6 @@ +namespace Spix.Domain.Users.Rules; + +public static class SpixerConstants +{ + public const int MaxContentLength = 280; +} diff --git a/src/Spix.Domain/Users/User.cs b/src/Spix.Domain/Users/User.cs index af4c642..d64fa16 100644 --- a/src/Spix.Domain/Users/User.cs +++ b/src/Spix.Domain/Users/User.cs @@ -4,7 +4,7 @@ namespace Spix.Domain.Users; -public class User : Entity +public class User : Entity, IAggregateRoot { public string UserName { get; private set; } @@ -15,8 +15,8 @@ public class User : Entity public List Spixers { get; private set; } = new(); public List SpixerLikes { get; private set; } = new(); - public virtual List Followers { get; private set; } = new(); - public virtual List Following { get; private set; } = new(); + public List Followers { get; private set; } = new(); + public List Following { get; private set; } = new(); @@ -25,7 +25,7 @@ public class User : Entity public string Location { get; private set; } public string WebSite { get; set; } - public DateTime CreatedAt { get; private set; } = DateTime.Now; + public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; private set; } = null; public IReadOnlyList GetSpixers() => Spixers.AsReadOnly(); diff --git a/src/Spix.Domain/Users/UserLanguage.cs b/src/Spix.Domain/Users/UserLanguage.cs index 382e48f..23f7563 100644 --- a/src/Spix.Domain/Users/UserLanguage.cs +++ b/src/Spix.Domain/Users/UserLanguage.cs @@ -1,7 +1,6 @@ namespace Spix.Domain.Users; -public class UserLanguage +public enum UserLanguage { - public int Id { get; set; } - public string Name { get; set; } + Portuguese = 1 } diff --git a/src/Spix.Infra/Database/Context/SpixDbContext.cs b/src/Spix.Infra/Database/Context/SpixDbContext.cs index f02025f..e989eb0 100644 --- a/src/Spix.Infra/Database/Context/SpixDbContext.cs +++ b/src/Spix.Infra/Database/Context/SpixDbContext.cs @@ -5,6 +5,8 @@ using Spix.Infra.Extensions; using Spix.Domain.Core; +using Spix.Domain.Users; +using Spix.Domain.Likes; namespace Spix.Infra.Database.Context; @@ -21,17 +23,28 @@ public SpixDbContext(DbContextOptions options, IMediator mediator public DbSet Spixers { get; init; } + public DbSet Users { get; init; } + public DbSet SpixerLikes { get; init; } public async Task CommitAsync(CancellationToken cancellationToken = default) { - await this._mediator.DispatchDomainEventsAsync(this, cancellationToken); - return await base.SaveChangesAsync(cancellationToken) > 0 ; + var result = await base.SaveChangesAsync(cancellationToken) > 0; + + await this._mediator.DispatchDomainEventsAsync(this, cancellationToken); + return result; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if (typeof(Entity).IsAssignableFrom(entityType.ClrType)) + { + modelBuilder.Entity(entityType.ClrType).Ignore("_domainEvents"); + } + } modelBuilder.ApplyConfigurationsFromAssembly(typeof(SpixDbContext).Assembly); } diff --git a/src/Spix.Infra/Database/Mapping/SpixerLikeMapping.cs b/src/Spix.Infra/Database/Mapping/SpixerLikeMapping.cs new file mode 100644 index 0000000..0d271fe --- /dev/null +++ b/src/Spix.Infra/Database/Mapping/SpixerLikeMapping.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Spix.Domain.Likes; + +namespace Spix.Infra.Database.Mapping; +public class SpixerLikeMapping : IEntityTypeConfiguration +{ + + public void Configure(EntityTypeBuilder builder) + { + builder + .ToTable("spixer_likes"); + + builder + .HasKey(x => x.Id); + builder + .Property(x => x.Id) + .HasColumnName("like_id"); + + builder.Property(x => x.SpixerId) + .HasColumnName("spixer_id"); + + builder.Property(x => x.UserId) + .HasColumnName("user_id"); + + builder.Property(x => x.CreatedAt) + .HasColumnName("created_at") + .HasConversion(v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + + builder.HasOne(x => x.Spixer) + .WithMany(s => s.SpixerLikes) + .HasForeignKey(x => x.SpixerId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + + } +} \ No newline at end of file diff --git a/src/Spix.Infra/Database/Mapping/SpixerMapping.cs b/src/Spix.Infra/Database/Mapping/SpixerMapping.cs index e9c8958..6f04741 100644 --- a/src/Spix.Infra/Database/Mapping/SpixerMapping.cs +++ b/src/Spix.Infra/Database/Mapping/SpixerMapping.cs @@ -1,9 +1,7 @@ - - -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Spix.Domain.Spixers; - +using Spix.Domain.Users.Rules; namespace Spix.Infra.Database.Mapping; public class SpixerMapping : IEntityTypeConfiguration @@ -11,17 +9,43 @@ public class SpixerMapping : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.ToTable("spixers"); + builder.HasKey(x => x.Id); - builder.Property(x => x.Id).HasColumnName("id"); - builder.Ignore(x => x._domainEvents); - builder.Ignore(x => x.LikedByUsers); - builder.Ignore(x => x.LikesCount); - builder.Property(x => x.Content).HasColumnName("content"); - builder.Property(x => x.CreatedAt).HasColumnName("created_at"); - builder.Property(x => x.UserId).HasColumnName("user_id"); - builder.Property(x => x.Active).HasColumnName("active"); + builder.Property(x => x.Id) + .HasColumnName("spixer_id"); + + builder.Property(x => x.Content) + .HasColumnName("content") + .HasColumnType("varchar") + .HasMaxLength(SpixerConstants.MaxContentLength); + + builder.Property(x => x.LikesCount) + .HasColumnName("likes_count") + .HasDefaultValue(0); + + builder.Property(x => x.CreatedAt) + .HasColumnName("created_at") + .HasConversion(v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); ; + builder.Property(x => x.UserId) + .HasColumnName("user_id"); + + builder.Property(x => x.Active) + .HasColumnName("active"); + + + builder.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasMany(x => x.SpixerLikes) + .WithOne(x => x.Spixer) + .HasForeignKey(x => x.SpixerId) + .OnDelete(DeleteBehavior.Cascade); } } + diff --git a/src/Spix.Infra/Database/Mapping/UserMapping.cs b/src/Spix.Infra/Database/Mapping/UserMapping.cs new file mode 100644 index 0000000..24f6cc5 --- /dev/null +++ b/src/Spix.Infra/Database/Mapping/UserMapping.cs @@ -0,0 +1,84 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Spix.Domain.Users; + +namespace Spix.Infra.Database.Mapping; + +public class UserMapping : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("users"); + + builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .HasColumnName("user_id"); + + builder.Property(x => x.UserName) + .HasColumnName("username") + .IsRequired() + .HasMaxLength(50); + + builder.OwnsOne(x => x.Attributes, a => + { + a.Property(p => p.FirstName).HasColumnName("first_name").HasMaxLength(50); + a.Property(p => p.LastName).HasColumnName("last_name").HasMaxLength(50); + a.Property(p => p.BirthDate).HasColumnName("birth_date"); + a.Property(p => p.Country).HasColumnName("country").HasMaxLength(50); + a.Property(p => p.City).HasColumnName("city").HasMaxLength(50); + + a.Property(p => p.Language).HasColumnName("user_language"); + }); + + + builder.OwnsOne(user => user.Email, email => + { + email.Property(e => e.Value) + .HasColumnName("email") + .IsRequired() + .HasConversion( + v => v.ToUpper(), + v => v.ToLower() + ); + }); + + builder.Property(x => x.Biography) + .HasColumnName("biography"); + + builder.Property(x => x.EmailIsVerified) + .HasColumnName("email_is_verified"); + + builder.Property(x => x.IsActive) + .HasColumnName("is_active"); + + builder.Property(x => x.Location) + .HasColumnName("user_location"); + + builder.Property(x => x.WebSite) + .HasColumnName("website"); + + builder.Property(x => x.CreatedAt) + .HasColumnName("created_at") + .HasConversion(v => v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); ; + + builder.Property(x => x.UpdatedAt) + .HasColumnName("updated_at") + .HasConversion( + v => v.HasValue ? v.Value.ToUniversalTime() : (DateTime?)null, + v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : (DateTime?)null + ); + + builder.HasMany(x => x.Spixers) + .WithOne(s => s.User) + .HasForeignKey(s => s.UserId); + + builder.HasMany(x => x.SpixerLikes) + .WithOne(sl => sl.User) + .HasForeignKey(sl => sl.UserId); + + builder.Ignore(x => x.Followers); + builder.Ignore(x => x.Following); + + } +} \ No newline at end of file diff --git a/src/Spix.Infra/Database/Repositories/SpixerRepository.cs b/src/Spix.Infra/Database/Repositories/SpixerRepository.cs index a37d69c..5029b7c 100644 --- a/src/Spix.Infra/Database/Repositories/SpixerRepository.cs +++ b/src/Spix.Infra/Database/Repositories/SpixerRepository.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Spix.Domain.Core; +using Spix.Domain.Likes; using Spix.Domain.Spixers; using Spix.Infra.Database.Context; @@ -9,11 +10,13 @@ public class SpixerRepository : ISpixerRepository { private readonly SpixDbContext spixDbContext; private readonly DbSet Spixers; + private readonly DbSet SpixerLikes; public SpixerRepository(SpixDbContext spixDbContext) { this.spixDbContext = spixDbContext; this.Spixers = spixDbContext.Spixers; + this.SpixerLikes = spixDbContext.SpixerLikes; } public IUnitOfWork UnitOfWork => spixDbContext; @@ -44,4 +47,14 @@ public Task UpdateAsync(Spixer entity) Task.Run(() => Spixers.Update(entity)); return Task.CompletedTask; } + + public async Task IsSpixerLikedByUserAsync(Guid spixerId, Guid userId) + { + return await Spixers.AnyAsync(x => x.Id == spixerId && x.SpixerLikes.Any(x => x.UserId == userId)); + } + public async Task AddSpixerLikeAsync(SpixerLike spixerLike) + { + await SpixerLikes.AddAsync(spixerLike); + } + } diff --git a/src/Spix.Infra/Database/Repositories/UserRepositories.cs b/src/Spix.Infra/Database/Repositories/UserRepositories.cs deleted file mode 100644 index 124392e..0000000 --- a/src/Spix.Infra/Database/Repositories/UserRepositories.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Spix.Domain.Users; - -namespace Spix.Infra.Database.Repositories; - -public class UserRepository : IUserRepository -{ - private List users = new(); - public Task AddAsync(User user) - { - users.Add(user); - return Task.FromResult (user); - } - - public Task GetAsync(Guid id) - { - return Task.FromResult(users.FirstOrDefault(x => x.Id == id)); - } -} diff --git a/src/Spix.Infra/Database/Repositories/UserRepository.cs b/src/Spix.Infra/Database/Repositories/UserRepository.cs new file mode 100644 index 0000000..8cd7c2b --- /dev/null +++ b/src/Spix.Infra/Database/Repositories/UserRepository.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; +using Spix.Domain.Core; +using Spix.Domain.Users; +using Spix.Infra.Database.Context; + +namespace Spix.Infra.Database.Repositories; + +public class UserRepository : IUserRepository +{ + private readonly SpixDbContext spixDbContext; + private readonly DbSet Users; + + public UserRepository(SpixDbContext spixDbContext) + { + this.spixDbContext = spixDbContext; + this.Users = spixDbContext.Users; + } + public IUnitOfWork UnitOfWork => spixDbContext; + + public Task AddAsync(User user) + { + Users.Add(user); + return Task.FromResult (user); + } + + public Task DeleteAsync(User entity) + { + Users.Remove(entity); + return Task.CompletedTask; + } + + public void Dispose() + { + spixDbContext.Dispose(); + } + + + + public async Task GetByIdAsync(Guid id) + { + return await Users.FirstOrDefaultAsync(x => x.Id == id); + } + + public Task UpdateAsync(User entity) + { + return Task.Run(() => Users.Update(entity)); + } +} From 6f22e33cb9cdb8c8dc39f7cbf2541290a5dd01c0 Mon Sep 17 00:00:00 2001 From: pedronvasconcelos Date: Sat, 24 Feb 2024 23:07:50 -0300 Subject: [PATCH 2/3] add: unlike + relationship between followers --- sql/create_db.sql | 21 +++++++- src/Spix.Api/Controllers/SpixerController.cs | 11 +++- .../Spixers/Unlike/UnlikeASpixerCommand.cs | 9 ++++ .../Unlike/UnlikeASpixerCommandHandler.cs | 53 +++++++++++++++++++ .../Spixers/Unlike/UnlikeASpixerResponse.cs | 13 +++++ src/Spix.Domain/Spixers/ISpixerRepository.cs | 3 ++ src/Spix.Domain/Users/User.cs | 17 ++++-- .../Database/Mapping/UserMapping.cs | 32 ++++++++++- .../Database/Repositories/SpixerRepository.cs | 9 ++++ 9 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommand.cs create mode 100644 src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommandHandler.cs create mode 100644 src/Spix.Application/Spixers/Unlike/UnlikeASpixerResponse.cs diff --git a/sql/create_db.sql b/sql/create_db.sql index f9b0580..14eadb7 100644 --- a/sql/create_db.sql +++ b/sql/create_db.sql @@ -18,7 +18,7 @@ CREATE TABLE users ( ); CREATE TABLE spixers ( - spider_id UUID PRIMARY KEY, + spixer_id UUID PRIMARY KEY, content VARCHAR(280), likes_count INT DEFAULT 0, created_at timestamp with time zone, @@ -32,6 +32,23 @@ CREATE TABLE spixer_likes ( spixer_id UUID, user_id UUID, created_at timestamp WITH TIME ZONE, - FOREIGN KEY (spixer_id) REFERENCES spixers(spider_id) ON DELETE CASCADE, + FOREIGN KEY (spixer_id) REFERENCES spixers(spixer_id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE ); + +CREATE TABLE user_following ( + user_id UUID NOT NULL, + following_id UUID NOT NULL, + + CONSTRAINT pk_user_following PRIMARY KEY (user_id, following_id), + + CONSTRAINT fk_user_following_user + FOREIGN KEY (user_id) + REFERENCES users (user_id) + ON DELETE RESTRICT, + + CONSTRAINT fk_user_following_following + FOREIGN KEY (following_id) + REFERENCES users (user_id) + ON DELETE RESTRICT +); diff --git a/src/Spix.Api/Controllers/SpixerController.cs b/src/Spix.Api/Controllers/SpixerController.cs index cc3f94a..80a7f04 100644 --- a/src/Spix.Api/Controllers/SpixerController.cs +++ b/src/Spix.Api/Controllers/SpixerController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Spix.Application.Spixers.Create; using Spix.Application.Spixers.Like; +using Spix.Application.Spixers.Unlike; namespace Spix.Api.Controllers { @@ -31,6 +32,14 @@ public async Task LikeSpixer([FromBody] LikeASpixerCommand comman { var response = await _mediator.Send(command); return Ok(response); - } + } + + [HttpPost] + [Route("[action]")] + public async Task UnlikeSpixer([FromBody] UnlikeASpixerCommand command) + { + var response = await _mediator.Send(command); + return Ok(response); + } } } diff --git a/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommand.cs b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommand.cs new file mode 100644 index 0000000..ea35445 --- /dev/null +++ b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommand.cs @@ -0,0 +1,9 @@ +using Spix.Application.Core; + +namespace Spix.Application.Spixers.Unlike; + +public class UnlikeASpixerCommand : ICommandBase +{ + public Guid UserId { get; set; } + public Guid SpixerId { get; set; } +} diff --git a/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommandHandler.cs b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommandHandler.cs new file mode 100644 index 0000000..62d1ffe --- /dev/null +++ b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerCommandHandler.cs @@ -0,0 +1,53 @@ + +using Spix.Application.Core; +using Spix.Domain.Spixers; +using Spix.Domain.Users; + +namespace Spix.Application.Spixers.Unlike; + +public class UnlikeASpixerCommandHandler : ICommandHandlerBase +{ + private readonly ISpixerRepository _spixerRepository; + private readonly IUserRepository _userRepository; + + public UnlikeASpixerCommandHandler(ISpixerRepository spixerRepository, IUserRepository userRepository) + { + _spixerRepository = spixerRepository; + _userRepository = userRepository; + } + public async Task> Handle(UnlikeASpixerCommand request, CancellationToken cancellationToken) + { + + var user = await _userRepository.GetByIdAsync(request.UserId); + if (user == null) + { + return ResultBaseFactory.Failure("User not found"); + } + var spixer = await _spixerRepository.GetByIdAsync(request.SpixerId); + if (spixer == null) + { + return ResultBaseFactory.Failure("Spixer not found"); + } + + var like = await _spixerRepository.GetSpixerLikeAsync(request.SpixerId, request.UserId); + if(like == null) + { + return ResultBaseFactory.Failure("Spixer not liked"); + } + + spixer.Unlike(like); + await _spixerRepository.DeleteSpixerLikeAsync(like); + + + await _spixerRepository.UpdateAsync(spixer); + if (!await _spixerRepository.UnitOfWork.CommitAsync(cancellationToken)) + { + return ResultBaseFactory.Failure("Error liking spixer"); + } + + + + return ResultBaseFactory.Successful( + new UnlikeASpixerResponse(spixer.Id, request.UserId)); + } +} diff --git a/src/Spix.Application/Spixers/Unlike/UnlikeASpixerResponse.cs b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerResponse.cs new file mode 100644 index 0000000..0c35c63 --- /dev/null +++ b/src/Spix.Application/Spixers/Unlike/UnlikeASpixerResponse.cs @@ -0,0 +1,13 @@ +namespace Spix.Application.Spixers.Unlike; + +public class UnlikeASpixerResponse +{ + public Guid SpixerId { get; set; } + public Guid UserId { get; set; } + + public UnlikeASpixerResponse(Guid spixerId, Guid userId) + { + SpixerId = spixerId; + UserId = userId; + } +} \ No newline at end of file diff --git a/src/Spix.Domain/Spixers/ISpixerRepository.cs b/src/Spix.Domain/Spixers/ISpixerRepository.cs index 54e25fb..d2c003d 100644 --- a/src/Spix.Domain/Spixers/ISpixerRepository.cs +++ b/src/Spix.Domain/Spixers/ISpixerRepository.cs @@ -8,4 +8,7 @@ public interface ISpixerRepository : IRepository { Task IsSpixerLikedByUserAsync(Guid spixerId, Guid userId); Task AddSpixerLikeAsync(SpixerLike spixerLike); + + Task GetSpixerLikeAsync(Guid spixerId, Guid userId); + Task DeleteSpixerLikeAsync(SpixerLike spixerLike); } diff --git a/src/Spix.Domain/Users/User.cs b/src/Spix.Domain/Users/User.cs index d64fa16..a9f1fee 100644 --- a/src/Spix.Domain/Users/User.cs +++ b/src/Spix.Domain/Users/User.cs @@ -15,8 +15,8 @@ public class User : Entity, IAggregateRoot public List Spixers { get; private set; } = new(); public List SpixerLikes { get; private set; } = new(); - public List Followers { get; private set; } = new(); - public List Following { get; private set; } = new(); + public ICollection Following { get; private set; } + public ICollection Followers { get; private set; } @@ -34,13 +34,14 @@ public class User : Entity, IAggregateRoot public bool IsConfirmed() => EmailIsVerified && IsActive; public bool IsComplete() => Attributes is not null; - //construtor public User(string userName, Email email) { UserName = userName; Email = email; EmailIsVerified = false; IsActive = true; + Location = "Unknown"; + WebSite = "Unknown"; } public User() @@ -50,3 +51,13 @@ public User() } + + +public class UserFollower +{ + public Guid UserId { get; set; } + public User User { get; set; } + + public Guid FollowerId { get; set; } + public User Follower { get; set; } +} \ No newline at end of file diff --git a/src/Spix.Infra/Database/Mapping/UserMapping.cs b/src/Spix.Infra/Database/Mapping/UserMapping.cs index 24f6cc5..9875671 100644 --- a/src/Spix.Infra/Database/Mapping/UserMapping.cs +++ b/src/Spix.Infra/Database/Mapping/UserMapping.cs @@ -77,8 +77,36 @@ public void Configure(EntityTypeBuilder builder) .WithOne(sl => sl.User) .HasForeignKey(sl => sl.UserId); - builder.Ignore(x => x.Followers); - builder.Ignore(x => x.Following); + + + } + + + public class UserFollowerConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("user_followers"); + + builder.HasKey(uf => new { uf.UserId, uf.FollowerId }); + + builder.Property(uf => uf.UserId) + .HasColumnName("user_id"); // Define the column name for UserId + + builder.Property(uf => uf.FollowerId) + .HasColumnName("follower_id"); // Define the column name for FollowerId + + builder.HasOne(uf => uf.User) + .WithMany(u => u.Followers) + .HasForeignKey(uf => uf.UserId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(uf => uf.Follower) + .WithMany(u => u.Following) + .HasForeignKey(uf => uf.FollowerId) + .OnDelete(DeleteBehavior.Restrict); + + } } } \ No newline at end of file diff --git a/src/Spix.Infra/Database/Repositories/SpixerRepository.cs b/src/Spix.Infra/Database/Repositories/SpixerRepository.cs index 5029b7c..7f4e4e2 100644 --- a/src/Spix.Infra/Database/Repositories/SpixerRepository.cs +++ b/src/Spix.Infra/Database/Repositories/SpixerRepository.cs @@ -57,4 +57,13 @@ public async Task AddSpixerLikeAsync(SpixerLike spixerLike) await SpixerLikes.AddAsync(spixerLike); } + public async Task GetSpixerLikeAsync(Guid spixerId, Guid userId) + { + return await SpixerLikes.FirstOrDefaultAsync(x => x.SpixerId == spixerId && x.UserId == userId); + } + public async Task DeleteSpixerLikeAsync(SpixerLike spixerLike) + { + await Task.Run(() => SpixerLikes.Remove(spixerLike)); + } + } From 84d82c8cfae5b446e9f5587541dcd00bd0ab67c9 Mon Sep 17 00:00:00 2001 From: pedronvasconcelos Date: Sun, 25 Feb 2024 00:01:48 -0300 Subject: [PATCH 3/3] add: Delete a Spixer. --- src/Spix.Api/Controllers/SpixerController.cs | 30 +++++++++++++++ .../Spixers/Delete/DeleteSpixerCommand.cs | 15 ++++++++ .../Delete/DeleteSpixerCommandHandler.cs | 37 +++++++++++++++++++ .../Spixers/Delete/DeleteSpixerResponse.cs | 6 +++ src/Spix.Domain/Spixers/Spixer.cs | 4 ++ .../{Likes => Spixers}/SpixerLike.cs | 0 6 files changed, 92 insertions(+) create mode 100644 src/Spix.Application/Spixers/Delete/DeleteSpixerCommand.cs create mode 100644 src/Spix.Application/Spixers/Delete/DeleteSpixerCommandHandler.cs create mode 100644 src/Spix.Application/Spixers/Delete/DeleteSpixerResponse.cs rename src/Spix.Domain/{Likes => Spixers}/SpixerLike.cs (100%) diff --git a/src/Spix.Api/Controllers/SpixerController.cs b/src/Spix.Api/Controllers/SpixerController.cs index 80a7f04..b663008 100644 --- a/src/Spix.Api/Controllers/SpixerController.cs +++ b/src/Spix.Api/Controllers/SpixerController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Spix.Application.Spixers.Create; +using Spix.Application.Spixers.Delete; using Spix.Application.Spixers.Like; using Spix.Application.Spixers.Unlike; @@ -22,6 +23,10 @@ public SpixerController(IMediator mediator) [Route("[action]")] public async Task CreateSpixer([FromBody] CreateSpixerCommand command) { + if(!ModelState.IsValid) + { + return BadRequest(ModelState); + } var response = await _mediator.Send(command); return Ok(response); } @@ -30,6 +35,11 @@ public async Task CreateSpixer([FromBody] CreateSpixerCommand com [Route("[action]")] public async Task LikeSpixer([FromBody] LikeASpixerCommand command) { + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } var response = await _mediator.Send(command); return Ok(response); } @@ -38,6 +48,26 @@ public async Task LikeSpixer([FromBody] LikeASpixerCommand comman [Route("[action]")] public async Task UnlikeSpixer([FromBody] UnlikeASpixerCommand command) { + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var response = await _mediator.Send(command); + return Ok(response); + } + + [HttpDelete] + [Route("[action]")] + public async Task DeleteSpixer([FromBody] Guid guid) + { + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var command = new DeleteSpixerCommand(Guid.NewGuid(), guid); var response = await _mediator.Send(command); return Ok(response); } diff --git a/src/Spix.Application/Spixers/Delete/DeleteSpixerCommand.cs b/src/Spix.Application/Spixers/Delete/DeleteSpixerCommand.cs new file mode 100644 index 0000000..7285f90 --- /dev/null +++ b/src/Spix.Application/Spixers/Delete/DeleteSpixerCommand.cs @@ -0,0 +1,15 @@ +using Spix.Application.Core; + +namespace Spix.Application.Spixers.Delete; + +public class DeleteSpixerCommand : ICommandBase +{ + public Guid UserId { get; set; } + public Guid SpixerId { get; set; } + + public DeleteSpixerCommand(Guid userId, Guid spixerId) + { + UserId = userId; + SpixerId = spixerId; + } +} diff --git a/src/Spix.Application/Spixers/Delete/DeleteSpixerCommandHandler.cs b/src/Spix.Application/Spixers/Delete/DeleteSpixerCommandHandler.cs new file mode 100644 index 0000000..cd64410 --- /dev/null +++ b/src/Spix.Application/Spixers/Delete/DeleteSpixerCommandHandler.cs @@ -0,0 +1,37 @@ +using Spix.Application.Core; +using Spix.Domain.Spixers; + +namespace Spix.Application.Spixers.Delete; + +public class DeleteSpixerCommandHandler : ICommandHandlerBase +{ + private readonly ISpixerRepository _spixerRepository; + + public DeleteSpixerCommandHandler(ISpixerRepository spixerRepository) + { + _spixerRepository = spixerRepository; + } + + public async Task> Handle(DeleteSpixerCommand request, CancellationToken cancellationToken) + { + + var spixer = await _spixerRepository.GetByIdAsync(request.SpixerId); + if (spixer == null) + { + return ResultBaseFactory.Failure("Spixer not found"); + } + + if (spixer.UserId != request.UserId) + { + return ResultBaseFactory.Failure("User not authorized to delete this spixer"); + } + + spixer.Delete(); + await _spixerRepository.UpdateAsync(spixer); + if (!await _spixerRepository.UnitOfWork.CommitAsync(cancellationToken)) + { + return ResultBaseFactory.Failure("Error deleting spixer"); + } + return ResultBaseFactory.Successful(new DeleteSpixerResponse { DeletedAt = DateTime.UtcNow }, "Success"); + } +} diff --git a/src/Spix.Application/Spixers/Delete/DeleteSpixerResponse.cs b/src/Spix.Application/Spixers/Delete/DeleteSpixerResponse.cs new file mode 100644 index 0000000..780a317 --- /dev/null +++ b/src/Spix.Application/Spixers/Delete/DeleteSpixerResponse.cs @@ -0,0 +1,6 @@ +namespace Spix.Application.Spixers.Delete; + +public class DeleteSpixerResponse +{ + public DateTime DeletedAt { get; set; } +} \ No newline at end of file diff --git a/src/Spix.Domain/Spixers/Spixer.cs b/src/Spix.Domain/Spixers/Spixer.cs index 2d2a04a..f4affb1 100644 --- a/src/Spix.Domain/Spixers/Spixer.cs +++ b/src/Spix.Domain/Spixers/Spixer.cs @@ -33,6 +33,10 @@ public void Unlike(SpixerLike like) } + public void Delete() + { + Active = false; + } public Spixer(string content, Guid userId) { SetContent(content); diff --git a/src/Spix.Domain/Likes/SpixerLike.cs b/src/Spix.Domain/Spixers/SpixerLike.cs similarity index 100% rename from src/Spix.Domain/Likes/SpixerLike.cs rename to src/Spix.Domain/Spixers/SpixerLike.cs