Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement delete endpoint for Files, Nodes, Notes, Reminders and Timelines module #41

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
64c274b
feat: implement delete file
HunorTotBagi Jan 4, 2025
a75924f
feat: implement delete `Node` endpoint
HunorTotBagi Jan 4, 2025
ad0c261
chore: rename response
HunorTotBagi Jan 4, 2025
38d41e9
chore: rename
HunorTotBagi Jan 5, 2025
ec8ca94
feat: implement delete `Note` endpoint
HunorTotBagi Jan 5, 2025
05ccb51
feat: implement delete `Reminder` endpoint
HunorTotBagi Jan 5, 2025
9229e77
feat: implement delete `Timeline` endpoint
HunorTotBagi Jan 5, 2025
e4ca881
chore: add empty lines and rename
HunorTotBagi Jan 5, 2025
4b75aa6
chore: fix formatting & delete redundant empty lines
HunorTotBagi Jan 13, 2025
679a0c4
Merge branch 'main' into feat/implement-delete-endpoint-for-all-entities
HunorTotBagi Jan 13, 2025
7a6dd85
Merge branch 'main' into feat/implement-delete-endpoint-for-all-entities
HunorTotBagi Jan 14, 2025
708dd41
feat: add validation for `Id` and variable rename
HunorTotBagi Jan 14, 2025
c8e37a4
feat: add validation for `Id` and variable rename for the rest of del…
HunorTotBagi Jan 14, 2025
f08f8e6
feat: Validate Ids sent as part of requests for the rest of the modules
HunorTotBagi Jan 14, 2025
14263e4
chore: remove variable and make adjustments to GetByIdHandlers
HunorTotBagi Jan 15, 2025
dbb5f48
chore: add empty line after ReSharper comment in GetByIdQuery files
HunorTotBagi Jan 15, 2025
208c22f
refactor: make delete commands strongly typed
HunorTotBagi Jan 15, 2025
b78fc26
Merge branch 'main' into feat/implement-delete-endpoint-for-all-entities
HunorTotBagi Jan 16, 2025
3dc8612
fix: rename class
HunorTotBagi Jan 16, 2025
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
@@ -0,0 +1,25 @@
using Files.Application.Entities.Files.Commands.DeleteFileAsset;

namespace Files.Api.Endpoints.Files;

public class DeleteFileAsset : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapDelete("/Files/{fileId}", async (string fileId, ISender sender) =>
{
var result = await sender.Send(new DeleteFileAssetCommand(fileId));
var response = result.Adapt<DeleteFileAssetResponse>();

return Results.Ok(response);
})
.WithName("DeleteFileAsset")
.Produces<DeleteFileAssetResponse>()
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status404NotFound)
.WithSummary("Delete File Asset")
.WithDescription("Delete File Asset");
}
}

public record DeleteFileAssetResponse(bool FileDeleted);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Files.Application.Entities.Files.Commands.DeleteFileAsset;

public record DeleteFileAssetCommand(string Id) : ICommand<DeleteFileAssetResult>;

public record DeleteFileAssetResult(bool FileDeleted);

public class DeleteFileAssetCommandValidator : AbstractValidator<DeleteFileAssetCommand>
{
public DeleteFileAssetCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Files.Application.Entities.Files.Exceptions;

namespace Files.Application.Entities.Files.Commands.DeleteFileAsset;

public class DeleteFileAssetHandler(IFilesDbContext dbContext) : ICommandHandler<DeleteFileAssetCommand, DeleteFileAssetResult>
{
public async Task<DeleteFileAssetResult> Handle(DeleteFileAssetCommand command, CancellationToken cancellationToken)
{
var fileAsset = await dbContext.FileAssets
.AsNoTracking()
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved
.SingleOrDefaultAsync(f => f.Id == FileAssetId.Of(Guid.Parse(command.Id)), cancellationToken);

if (fileAsset is null)
throw new FileAssetNotFoundException(command.Id);

dbContext.FileAssets.Remove(fileAsset);
await dbContext.SaveChangesAsync(cancellationToken);

return new DeleteFileAssetResult(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ internal class GetFileAssetByIdHandler(IFilesDbContext dbContext) : IQueryHandle
{
public async Task<GetFileAssetByIdResult> Handle(GetFileAssetByIdQuery query, CancellationToken cancellationToken)
{
var fileAssetId = query.Id.ToString();
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

var fileAsset = await dbContext.FileAssets
.AsNoTracking()
.SingleOrDefaultAsync(f => f.Id == FileAssetId.Of(Guid.Parse(query.Id)), cancellationToken);
.SingleOrDefaultAsync(f => f.Id == FileAssetId.Of(Guid.Parse(fileAssetId)), cancellationToken);
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

if (fileAsset is null)
throw new FileAssetNotFoundException(query.Id);
throw new FileAssetNotFoundException(fileAssetId);

return new GetFileAssetByIdResult(fileAsset.ToFileAssetDto());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@

namespace Files.Application.Entities.Files.Queries.GetFileAssetById;

public record GetFileAssetByIdQuery(string Id) : IQuery<GetFileAssetByIdResult>;
public record GetFileAssetByIdQuery(FileAssetId Id) : IQuery<GetFileAssetByIdResult>
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved
{
public GetFileAssetByIdQuery(string Id) : this(FileAssetId.Of(Guid.Parse(Id))) { }
}

// ReSharper disable once NotAccessedPositionalProperty.Global
public record GetFileAssetByIdResult(FileAssetDto FileAssetDto);

public class GetFileAssetByIdQueryValidator : AbstractValidator<GetFileAssetByIdQuery>
{
public GetFileAssetByIdQueryValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Nodes.Application.Entities.Nodes.Commands.DeleteNode;

namespace Nodes.Api.Endpoints.Nodes;

public class DeleteNode : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapDelete("/Nodes/{nodeId}", async (string nodeId, ISender sender) =>
{
var result = await sender.Send(new DeleteNodeCommand(nodeId));
var response = result.Adapt<DeleteNodeResponse>();

return Results.Ok(response);
})
.WithName("DeleteNode")
.Produces<DeleteNodeResponse>()
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status404NotFound)
.WithSummary("Delete Node")
.WithDescription("Delete Node");
}
}

public record DeleteNodeResponse(bool NodeDeleted);
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Nodes.Application.Entities.Nodes.Commands.DeleteNode;

public record DeleteNodeCommand(string Id) : ICommand<DeleteNodeResult>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said above, I suggest strongly typed properties in commands and queries. This goes for all modules.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes made ✅

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, they don't seem to be - the Id is still of string type.

Copy link
Collaborator Author

@HunorTotBagi HunorTotBagi Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That quoted code is outdated, locally and on GitHub I can see the correct version which is the Id type.

image


public record DeleteNodeResult(bool NodeDeleted);

public class DeleteNodeCommandValidator : AbstractValidator<DeleteNodeCommand>
{
public DeleteNodeCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Nodes.Application.Entities.Nodes.Exceptions;

namespace Nodes.Application.Entities.Nodes.Commands.DeleteNode;

public class DeleteNodeHandler(INodesDbContext dbContext) : ICommandHandler<DeleteNodeCommand, DeleteNodeResult>
{
public async Task<DeleteNodeResult> Handle(DeleteNodeCommand command, CancellationToken cancellationToken)
{
var node = await dbContext.Nodes
.AsNoTracking()
.SingleOrDefaultAsync(n => n.Id == NodeId.Of(Guid.Parse(command.Id)), cancellationToken);

if (node is null)
throw new NodeNotFoundException(command.Id);

dbContext.Nodes.Remove(node);
await dbContext.SaveChangesAsync(cancellationToken);

return new DeleteNodeResult(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Notes.Application.Entities.Notes.Commands.DeleteNote;

namespace Notes.Api.Endpoints.Notes;

public class DeleteNote : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapDelete("/Notes/{noteId}", async (string noteId, ISender sender) =>
{
var result = await sender.Send(new DeleteNoteCommand(noteId));
var response = result.Adapt<DeleteNoteResponse>();

return Results.Ok(response);
})
.WithName("DeleteNote")
.Produces<DeleteNoteResponse>()
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status404NotFound)
.WithSummary("Delete Note")
.WithDescription("Delete Note");
}
}

public record DeleteNoteResponse(bool NoteDeleted);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Notes.Application.Entities.Notes.Commands.DeleteNote;

public record DeleteNoteCommand(string Id) : ICommand<DeleteNoteResult>;

public record DeleteNoteResult(bool NoteDeleted);

public class DeleteNoteCommandValidator : AbstractValidator<DeleteNoteCommand>
{
public DeleteNoteCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Notes.Application.Entities.Notes.Exceptions;

namespace Notes.Application.Entities.Notes.Commands.DeleteNote;

public class DeleteNoteHandler(INotesDbContext dbContext) : ICommandHandler<DeleteNoteCommand, DeleteNoteResult>
{
public async Task<DeleteNoteResult> Handle(DeleteNoteCommand command, CancellationToken cancellationToken)
{
var note = await dbContext.Notes
.AsNoTracking()
.SingleOrDefaultAsync(n => n.Id == NoteId.Of(Guid.Parse(command.Id)), cancellationToken);

if (note is null)
throw new NoteNotFoundException(command.Id);

dbContext.Notes.Remove(note);
await dbContext.SaveChangesAsync(cancellationToken);

return new DeleteNoteResult(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ internal class GetNoteByIdHandler(INotesDbContext dbContext) : IQueryHandler<Get
{
public async Task<GetNoteByIdResult> Handle(GetNoteByIdQuery query, CancellationToken cancellationToken)
{
var noteId = query.Id.ToString();
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

var note = await dbContext.Notes
.AsNoTracking()
.SingleOrDefaultAsync(n => n.Id == NoteId.Of(Guid.Parse(query.Id)), cancellationToken);
.SingleOrDefaultAsync(n => n.Id == NoteId.Of(Guid.Parse(noteId)), cancellationToken);
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

if (note is null)
throw new NoteNotFoundException(query.Id);
throw new NoteNotFoundException(noteId);

return new GetNoteByIdResult(note.ToNoteDto());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
using Notes.Application.Entities.Notes.Dtos;
// ReSharper disable ClassNeverInstantiated.Global

using Notes.Application.Entities.Notes.Dtos;

namespace Notes.Application.Entities.Notes.Queries.GetNoteById;

public record GetNoteByIdQuery(string Id) : IQuery<GetNoteByIdResult>;
public record GetNoteByIdQuery(NoteId Id) : IQuery<GetNoteByIdResult>
{
public GetNoteByIdQuery(string Id) : this(NoteId.Of(Guid.Parse(Id))) { }
}

// ReSharper disable once NotAccessedPositionalProperty.Global
public record GetNoteByIdResult(NoteDto NoteDto);
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

public class GetNoteByIdQueryValidator : AbstractValidator<GetNoteByIdQuery>
{
public GetNoteByIdQueryValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Reminders.Application.Entities.Reminders.Commands.DeleteReminder;

namespace Reminders.Api.Endpoints.Reminders;

public class DeleteReminder : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapDelete("/Reminders/{reminderId}", async (string reminderId, ISender sender) =>
{
var result = await sender.Send(new DeleteReminderCommand(reminderId));
var response = result.Adapt<DeleteReminderResponse>();

return Results.Ok(response);
})
.WithName("DeleteReminder")
.Produces<DeleteReminderResponse>()
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status404NotFound)
.WithSummary("Delete Reminder")
.WithDescription("Delete Reminder");
}
}

public record DeleteReminderResponse(bool ReminderDeleted);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Reminders.Application.Entities.Reminders.Commands.DeleteReminder;

public record DeleteReminderCommand(string Id) : ICommand<DeleteReminderResult>;
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

public record DeleteReminderResult(bool ReminderDeleted);

public class DeleteReminderCommandValidator : AbstractValidator<DeleteReminderCommand>
{
public DeleteReminderCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Reminders.Application.Entities.Reminders.Exceptions;

namespace Reminders.Application.Entities.Reminders.Commands.DeleteReminder;

public class DeleteReminderHandler(IRemindersDbContext dbContext) : ICommandHandler<DeleteReminderCommand, DeleteReminderResult>
{
public async Task<DeleteReminderResult> Handle(DeleteReminderCommand command, CancellationToken cancellationToken)
{
var reminder = await dbContext.Reminders
.AsNoTracking()
.SingleOrDefaultAsync(r => r.Id == ReminderId.Of(Guid.Parse(command.Id)), cancellationToken);

if (reminder is null)
throw new ReminderNotFoundException(command.Id);

dbContext.Reminders.Remove(reminder);
await dbContext.SaveChangesAsync(cancellationToken);

return new DeleteReminderResult(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ internal class GetReminderByIdHandler(IRemindersDbContext dbContext) : IQueryHan
{
public async Task<GetReminderByIdResult> Handle(GetReminderByIdQuery query, CancellationToken cancellationToken)
{
var reminderId = query.Id.ToString();

var reminder = await dbContext.Reminders
.AsNoTracking()
.SingleOrDefaultAsync(r => r.Id == ReminderId.Of(Guid.Parse(query.Id)), cancellationToken);
.SingleOrDefaultAsync(r => r.Id == ReminderId.Of(Guid.Parse(reminderId)), cancellationToken);
NikolaVetnic marked this conversation as resolved.
Show resolved Hide resolved

if (reminder is null)
throw new ReminderNotFoundException(query.Id);
throw new ReminderNotFoundException(reminderId);

return new GetReminderByIdResult(reminder.ToReminderDto());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@

namespace Reminders.Application.Entities.Reminders.Queries.GetReminderById;

public record GetReminderByIdQuery(string Id) : IQuery<GetReminderByIdResult>;
public record GetReminderByIdQuery(ReminderId Id) : IQuery<GetReminderByIdResult>
{
public GetReminderByIdQuery(string Id) : this(ReminderId.Of(Guid.Parse(Id))) { }
}

// ReSharper disable once NotAccessedPositionalProperty.Global
public record GetReminderByIdResult(ReminderDto ReminderDto);

public class GetReminderByIdQueryValidator : AbstractValidator<GetReminderByIdQuery>
{
public GetReminderByIdQueryValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.")
.Must(value => Guid.TryParse(value.ToString(), out _)).WithMessage("Id is not valid.");
}
}
Loading
Loading