Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CCE.Api.Common.Extensions;
using CCE.Application.Community.Public.Queries.ListPublicTopics;
using MediatR;
using Microsoft.AspNetCore.Builder;
Expand All @@ -14,8 +15,8 @@ public static IEndpointRouteBuilder MapTopicsPublicEndpoints(this IEndpointRoute

topics.MapGet("", async (IMediator mediator, CancellationToken ct) =>
{
var result = await mediator.Send(new ListPublicTopicsQuery(), ct).ConfigureAwait(false);
return Results.Ok(result);
var response = await mediator.Send(new ListPublicTopicsQuery(), ct).ConfigureAwait(false);
return response.ToHttpResult();
})
.AllowAnonymous()
.WithName("ListPublicTopics");
Expand Down
21 changes: 11 additions & 10 deletions backend/src/CCE.Api.Internal/Endpoints/ResourceCategoryEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CCE.Api.Common.Extensions;
using CCE.Application.Content.Commands.CreateResourceCategory;
using CCE.Application.Content.Commands.DeleteResourceCategory;
using CCE.Application.Content.Commands.UpdateResourceCategory;
Expand Down Expand Up @@ -26,8 +27,8 @@ public static IEndpointRouteBuilder MapResourceCategoryEndpoints(this IEndpointR
PageSize: pageSize ?? 20,
ParentId: parentId,
IsActive: isActive);
var result = await mediator.Send(query, cancellationToken).ConfigureAwait(false);
return Results.Ok(result);
var response = await mediator.Send(query, cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Resource_Center_Upload)
.WithName("ListResourceCategories");
Expand All @@ -36,8 +37,8 @@ public static IEndpointRouteBuilder MapResourceCategoryEndpoints(this IEndpointR
System.Guid id,
IMediator mediator, CancellationToken cancellationToken) =>
{
var dto = await mediator.Send(new GetResourceCategoryByIdQuery(id), cancellationToken).ConfigureAwait(false);
return dto is null ? Results.NotFound() : Results.Ok(dto);
var response = await mediator.Send(new GetResourceCategoryByIdQuery(id), cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Resource_Center_Upload)
.WithName("GetResourceCategoryById");
Expand All @@ -48,8 +49,8 @@ public static IEndpointRouteBuilder MapResourceCategoryEndpoints(this IEndpointR
{
var cmd = new CreateResourceCategoryCommand(
body.NameAr, body.NameEn, body.Slug, body.ParentId, body.OrderIndex);
var dto = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return Results.Created($"/api/admin/resource-categories/{dto.Id}", dto);
var response = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return response.ToCreatedHttpResult();
})
.RequireAuthorization(Permissions.Resource_Center_Upload)
.WithName("CreateResourceCategory");
Expand All @@ -61,8 +62,8 @@ public static IEndpointRouteBuilder MapResourceCategoryEndpoints(this IEndpointR
{
var cmd = new UpdateResourceCategoryCommand(
id, body.NameAr, body.NameEn, body.OrderIndex, body.IsActive);
var dto = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return dto is null ? Results.NotFound() : Results.Ok(dto);
var response = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Resource_Center_Upload)
.WithName("UpdateResourceCategory");
Expand All @@ -71,8 +72,8 @@ public static IEndpointRouteBuilder MapResourceCategoryEndpoints(this IEndpointR
System.Guid id,
IMediator mediator, CancellationToken cancellationToken) =>
{
await mediator.Send(new DeleteResourceCategoryCommand(id), cancellationToken).ConfigureAwait(false);
return Results.NoContent();
var response = await mediator.Send(new DeleteResourceCategoryCommand(id), cancellationToken).ConfigureAwait(false);
return response.ToNoContentHttpResult();
})
.RequireAuthorization(Permissions.Resource_Center_Upload)
.WithName("DeleteResourceCategory");
Expand Down
21 changes: 11 additions & 10 deletions backend/src/CCE.Api.Internal/Endpoints/TopicEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CCE.Api.Common.Extensions;
using CCE.Application.Community.Commands.CreateTopic;
using CCE.Application.Community.Commands.DeleteTopic;
using CCE.Application.Community.Commands.UpdateTopic;
Expand Down Expand Up @@ -27,8 +28,8 @@ public static IEndpointRouteBuilder MapTopicEndpoints(this IEndpointRouteBuilder
ParentId: parentId,
IsActive: isActive,
Search: search);
var result = await mediator.Send(query, cancellationToken).ConfigureAwait(false);
return Results.Ok(result);
var response = await mediator.Send(query, cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Community_Post_Moderate)
.WithName("ListTopics");
Expand All @@ -37,8 +38,8 @@ public static IEndpointRouteBuilder MapTopicEndpoints(this IEndpointRouteBuilder
System.Guid id,
IMediator mediator, CancellationToken cancellationToken) =>
{
var dto = await mediator.Send(new GetTopicByIdQuery(id), cancellationToken).ConfigureAwait(false);
return dto is null ? Results.NotFound() : Results.Ok(dto);
var response = await mediator.Send(new GetTopicByIdQuery(id), cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Community_Post_Moderate)
.WithName("GetTopicById");
Expand All @@ -51,8 +52,8 @@ public static IEndpointRouteBuilder MapTopicEndpoints(this IEndpointRouteBuilder
body.NameAr, body.NameEn,
body.DescriptionAr, body.DescriptionEn,
body.Slug, body.ParentId, body.IconUrl, body.OrderIndex);
var dto = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return Results.Created($"/api/admin/topics/{dto.Id}", dto);
var response = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return response.ToCreatedHttpResult();
})
.RequireAuthorization(Permissions.Community_Post_Moderate)
.WithName("CreateTopic");
Expand All @@ -66,8 +67,8 @@ public static IEndpointRouteBuilder MapTopicEndpoints(this IEndpointRouteBuilder
id, body.NameAr, body.NameEn,
body.DescriptionAr, body.DescriptionEn,
body.OrderIndex, body.IsActive);
var dto = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return dto is null ? Results.NotFound() : Results.Ok(dto);
var response = await mediator.Send(cmd, cancellationToken).ConfigureAwait(false);
return response.ToHttpResult();
})
.RequireAuthorization(Permissions.Community_Post_Moderate)
.WithName("UpdateTopic");
Expand All @@ -76,8 +77,8 @@ public static IEndpointRouteBuilder MapTopicEndpoints(this IEndpointRouteBuilder
System.Guid id,
IMediator mediator, CancellationToken cancellationToken) =>
{
await mediator.Send(new DeleteTopicCommand(id), cancellationToken).ConfigureAwait(false);
return Results.NoContent();
var response = await mediator.Send(new DeleteTopicCommand(id), cancellationToken).ConfigureAwait(false);
return response.ToNoContentHttpResult();
})
.RequireAuthorization(Permissions.Community_Post_Moderate)
.WithName("DeleteTopic");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CCE.Application.Common;
using CCE.Application.Community.Dtos;
using MediatR;

Expand All @@ -11,4 +12,4 @@ public sealed record CreateTopicCommand(
string Slug,
System.Guid? ParentId,
string? IconUrl,
int OrderIndex) : IRequest<TopicDto>;
int OrderIndex) : IRequest<Response<TopicDto>>;
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Community.Dtos;
using CCE.Application.Community.Queries.ListTopics;
using CCE.Application.Messages;
using CCE.Domain.Community;
using MediatR;

namespace CCE.Application.Community.Commands.CreateTopic;

public sealed class CreateTopicCommandHandler : IRequestHandler<CreateTopicCommand, TopicDto>
public sealed class CreateTopicCommandHandler : IRequestHandler<CreateTopicCommand, Response<TopicDto>>
{
private readonly ITopicService _service;
private readonly IRepository<Topic, System.Guid> _repo;
private readonly ICceDbContext _db;
private readonly MessageFactory _messages;

public CreateTopicCommandHandler(ITopicService service)
public CreateTopicCommandHandler(
IRepository<Topic, System.Guid> repo,
ICceDbContext db,
MessageFactory messages)
{
_service = service;
_repo = repo;
_db = db;
_messages = messages;
}

public async Task<TopicDto> Handle(CreateTopicCommand request, CancellationToken cancellationToken)
public async Task<Response<TopicDto>> Handle(CreateTopicCommand request, CancellationToken cancellationToken)
{
var topic = Topic.Create(
request.NameAr,
Expand All @@ -26,8 +36,9 @@ public async Task<TopicDto> Handle(CreateTopicCommand request, CancellationToken
request.IconUrl,
request.OrderIndex);

await _service.SaveAsync(topic, cancellationToken).ConfigureAwait(false);
await _repo.AddAsync(topic, cancellationToken).ConfigureAwait(false);
await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

return ListTopicsQueryHandler.MapToDto(topic);
return _messages.Ok(ListTopicsQueryHandler.MapToDto(topic), "CONTENT_CREATED");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CCE.Application.Common;
using MediatR;

namespace CCE.Application.Community.Commands.DeleteTopic;

public sealed record DeleteTopicCommand(System.Guid Id) : IRequest<Unit>;
public sealed record DeleteTopicCommand(System.Guid Id) : IRequest<Response<VoidData>>;
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Messages;
using CCE.Domain.Common;
using CCE.Domain.Community;
using MediatR;

namespace CCE.Application.Community.Commands.DeleteTopic;

public sealed class DeleteTopicCommandHandler : IRequestHandler<DeleteTopicCommand, Unit>
public sealed class DeleteTopicCommandHandler : IRequestHandler<DeleteTopicCommand, Response<VoidData>>
{
private readonly ITopicService _service;
private readonly IRepository<Topic, System.Guid> _repo;
private readonly ICceDbContext _db;
private readonly ICurrentUserAccessor _currentUser;
private readonly ISystemClock _clock;
private readonly MessageFactory _messages;

public DeleteTopicCommandHandler(ITopicService service, ICurrentUserAccessor currentUser, ISystemClock clock)
public DeleteTopicCommandHandler(
IRepository<Topic, System.Guid> repo,
ICceDbContext db,
ICurrentUserAccessor currentUser,
ISystemClock clock,
MessageFactory messages)
{
_service = service;
_repo = repo;
_db = db;
_currentUser = currentUser;
_clock = clock;
_messages = messages;
}

public async Task<Unit> Handle(DeleteTopicCommand request, CancellationToken cancellationToken)
public async Task<Response<VoidData>> Handle(DeleteTopicCommand request, CancellationToken cancellationToken)
{
var topic = await _service.FindAsync(request.Id, cancellationToken).ConfigureAwait(false);
var topic = await _repo.GetByIdAsync(request.Id, cancellationToken).ConfigureAwait(false);
if (topic is null)
{
throw new System.Collections.Generic.KeyNotFoundException($"Topic {request.Id} not found.");
}
return _messages.TopicNotFound<VoidData>();

var deletedById = _currentUser.GetUserId()
?? throw new DomainException("Cannot delete topic from a request without a user identity.");
var deletedById = _currentUser.GetUserId();
if (deletedById is null)
return _messages.NotAuthenticated<VoidData>();

topic.SoftDelete(deletedById, _clock);
await _service.UpdateAsync(topic, cancellationToken).ConfigureAwait(false);
return Unit.Value;
topic.SoftDelete(deletedById.Value, _clock);
await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

return _messages.Ok("CONTENT_DELETED");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CCE.Application.Common;
using CCE.Application.Community.Dtos;
using MediatR;

Expand All @@ -10,4 +11,4 @@ public sealed record UpdateTopicCommand(
string DescriptionAr,
string DescriptionEn,
int OrderIndex,
bool IsActive) : IRequest<TopicDto?>;
bool IsActive) : IRequest<Response<TopicDto>>;
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Community.Dtos;
using CCE.Application.Community.Queries.ListTopics;
using CCE.Application.Messages;
using CCE.Domain.Community;
using MediatR;

namespace CCE.Application.Community.Commands.UpdateTopic;

public sealed class UpdateTopicCommandHandler : IRequestHandler<UpdateTopicCommand, TopicDto?>
public sealed class UpdateTopicCommandHandler : IRequestHandler<UpdateTopicCommand, Response<TopicDto>>
{
private readonly ITopicService _service;

public UpdateTopicCommandHandler(ITopicService service)
private readonly IRepository<Topic, System.Guid> _repo;
private readonly ICceDbContext _db;
private readonly MessageFactory _messages;

public UpdateTopicCommandHandler(
IRepository<Topic, System.Guid> repo,
ICceDbContext db,
MessageFactory messages)
{
_service = service;
_repo = repo;
_db = db;
_messages = messages;
}

public async Task<TopicDto?> Handle(UpdateTopicCommand request, CancellationToken cancellationToken)
public async Task<Response<TopicDto>> Handle(UpdateTopicCommand request, CancellationToken cancellationToken)
{
var topic = await _service.FindAsync(request.Id, cancellationToken).ConfigureAwait(false);
var topic = await _repo.GetByIdAsync(request.Id, cancellationToken).ConfigureAwait(false);
if (topic is null)
{
return null;
}
return _messages.TopicNotFound<TopicDto>();

topic.UpdateContent(request.NameAr, request.NameEn, request.DescriptionAr, request.DescriptionEn);
topic.Reorder(request.OrderIndex);
Expand All @@ -29,8 +38,8 @@ public UpdateTopicCommandHandler(ITopicService service)
else
topic.Deactivate();

await _service.UpdateAsync(topic, cancellationToken).ConfigureAwait(false);
await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

return ListTopicsQueryHandler.MapToDto(topic);
return _messages.Ok(ListTopicsQueryHandler.MapToDto(topic), "SUCCESS_OPERATION");
}
}
13 changes: 3 additions & 10 deletions backend/src/CCE.Application/Community/ITopicService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
using CCE.Domain.Community;

namespace CCE.Application.Community;

public interface ITopicService
{
Task SaveAsync(Topic topic, CancellationToken ct);
Task<Topic?> FindAsync(System.Guid id, CancellationToken ct);
Task UpdateAsync(Topic topic, CancellationToken ct);
}
// This interface is intentionally empty — Topic now uses
// IRepository<Topic, Guid> for all write operations.
// See CCE.Application.Common.Interfaces.IRepository<,>.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CCE.Application.Common;
using CCE.Application.Community.Public.Dtos;
using MediatR;

namespace CCE.Application.Community.Public.Queries.GetPublicTopicBySlug;

public sealed record GetPublicTopicBySlugQuery(string Slug) : IRequest<PublicTopicDto?>;
public sealed record GetPublicTopicBySlugQuery(string Slug) : IRequest<Response<PublicTopicDto>>;
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Common.Pagination;
using CCE.Application.Community.Public.Dtos;
using CCE.Application.Community.Public.Queries.ListPublicTopics;
using CCE.Application.Messages;
using MediatR;

namespace CCE.Application.Community.Public.Queries.GetPublicTopicBySlug;

public sealed class GetPublicTopicBySlugQueryHandler
: IRequestHandler<GetPublicTopicBySlugQuery, PublicTopicDto?>
: IRequestHandler<GetPublicTopicBySlugQuery, Response<PublicTopicDto>>
{
private readonly ICceDbContext _db;
private readonly MessageFactory _messages;

public GetPublicTopicBySlugQueryHandler(ICceDbContext db)
public GetPublicTopicBySlugQueryHandler(ICceDbContext db, MessageFactory messages)
{
_db = db;
_messages = messages;
}

public async Task<PublicTopicDto?> Handle(
public async Task<Response<PublicTopicDto>> Handle(
GetPublicTopicBySlugQuery request,
CancellationToken cancellationToken)
{
Expand All @@ -26,6 +30,9 @@ public GetPublicTopicBySlugQueryHandler(ICceDbContext db)
.ConfigureAwait(false))
.FirstOrDefault();

return topic is null ? null : ListPublicTopicsQueryHandler.MapToDto(topic);
if (topic is null)
return _messages.TopicNotFound<PublicTopicDto>();

return _messages.Ok(ListPublicTopicsQueryHandler.MapToDto(topic), "SUCCESS_OPERATION");
}
}
Loading