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

Update Timeline Model and Establish Relationship with Node Entity #67

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4bc989c
chore: delete `TimelineDto` folder from `Timelines.Application`
HunorTotBagi Jan 31, 2025
0c93bef
fix: namespace for `TimelineDto`
HunorTotBagi Jan 31, 2025
919922c
feat: add `TimelineBaseDto`
HunorTotBagi Jan 31, 2025
c6b1a55
feat: connect `TimelineDto` and `NodeDto`
HunorTotBagi Jan 31, 2025
7c3ca97
chore: delete `Events` from `Timeline.Domain`
HunorTotBagi Jan 31, 2025
4c395f2
fix: adapt timeline events
HunorTotBagi Jan 31, 2025
625713f
chore: move `TimelineId` into correct folder
HunorTotBagi Jan 31, 2025
b75a11b
feat: connect `Timeline` and `Node` entities model
HunorTotBagi Jan 31, 2025
3b0c6f6
chore: fix database seeding for Nodes
HunorTotBagi Jan 31, 2025
f8bdc76
chore: move `ITimelinesDbContext` under newly created `Abstractions` …
HunorTotBagi Jan 31, 2025
13de0b2
feat: add timelines repositories
HunorTotBagi Feb 3, 2025
6b107b5
Merge branch 'main' into jssbg-76_connect_timeline_and_node_entities
HunorTotBagi Feb 3, 2025
1daeac4
chore: rename methods
HunorTotBagi Feb 3, 2025
f036903
feat: add timelines service
HunorTotBagi Feb 3, 2025
bb0b05d
chore: fix Node DatabaseExtensions
HunorTotBagi Feb 4, 2025
81bc0e1
chore: fix command fields
HunorTotBagi Feb 5, 2025
a75dea1
fix: dbContext
HunorTotBagi Feb 5, 2025
eae5191
formatting
HunorTotBagi Feb 5, 2025
e5a084b
feat: deconstruct dto into request and command properties
HunorTotBagi Feb 5, 2025
dda8b89
chore: add empty line at the end of the file
HunorTotBagi Feb 5, 2025
9b90152
fix: typo & rename method
HunorTotBagi Feb 5, 2025
dedfc03
feat: add migrations
HunorTotBagi Feb 5, 2025
a248750
chore: remove unused using
HunorTotBagi Feb 5, 2025
3296a66
feat: add node to timeline when creating new node
HunorTotBagi Feb 5, 2025
24a149f
chore: remove duplicate timeline handler
HunorTotBagi Feb 10, 2025
c418637
feat: add setter for timelines
HunorTotBagi Feb 10, 2025
f101cf2
chore: rename `Timelines` to `Timeline`
HunorTotBagi Feb 10, 2025
3b3cc07
chore: fix method name to `GetTimelineByIdAsync`
HunorTotBagi Feb 10, 2025
e2f346c
fix: resolve problems with adapting NodeBaseDto
HunorTotBagi Feb 11, 2025
20faf61
fix: remove `Adapt` and utilise response on result for `GetNodeById` …
HunorTotBagi Feb 11, 2025
0cf6443
chore: remove unused usings & fix up `GlobalUsing` files
HunorTotBagi Feb 11, 2025
bb3175e
fix: use `await` keyword and utilize `GetTimelineBaseDtoAsync` instea…
HunorTotBagi Feb 11, 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
@@ -1,4 +1,4 @@
using BuildingBlocks.Domain.ValueObjects.Ids;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

namespace BuildingBlocks.Api.Converters;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using BuildingBlocks.Domain.Nodes.Node.ValueObjects;
using BuildingBlocks.Domain.Timelines.Timeline.Dtos;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

namespace BuildingBlocks.Application.Data;

public interface ITimelinesService
{
Task<TimelineDto> GetTimelineByIdAsync(TimelineId timelineId, CancellationToken cancellationToken);
Task<TimelineBaseDto> GetTimelineBaseDtoAsync(TimelineId timelineId, CancellationToken cancellationToken);
Task AddNode(TimelineId timelineId, NodeId nodeId, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using BuildingBlocks.Domain.Reminders.Reminder.Dtos;
using BuildingBlocks.Domain.Timelines.Timeline.Dtos;

namespace BuildingBlocks.Domain.Nodes.Node.Dtos;

Expand All @@ -11,7 +12,9 @@ public class NodeDto(
int importance,
string phase,
List<string> categories,
List<string> tags) : NodeBaseDto(id, title, description, timestamp, importance, phase, categories, tags)
List<string> tags,
TimelineBaseDto timeline) : NodeBaseDto(id, title, description, timestamp, importance, phase, categories, tags)
{
[JsonPropertyName("reminders")] public List<ReminderBaseDto> Reminders { get; } = [];
[JsonPropertyName("timelines")] public TimelineBaseDto Timeline { get; set; } = timeline;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Text.Json.Serialization;

namespace Timelines.Application.Entities.Timelines.Dtos;
namespace BuildingBlocks.Domain.Timelines.Timeline.Dtos;

public class TimelineDto(
public class TimelineBaseDto(
string? id,
string title)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using BuildingBlocks.Domain.Nodes.Node.Dtos;

namespace BuildingBlocks.Domain.Timelines.Timeline.Dtos;

public class TimelineDto(
string? id,
string title) : TimelineBaseDto(id, title)
{
[JsonPropertyName("nodes")] public List<NodeBaseDto> Nodes { get; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using BuildingBlocks.Domain.Abstractions;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

namespace BuildingBlocks.Domain.Timelines.Timeline.Events;

public record TimelineCreatedEvent(TimelineId TimelineId) : IDomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using BuildingBlocks.Domain.Abstractions;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

namespace BuildingBlocks.Domain.Timelines.Timeline.Events;

public record TimelineUpdatedEvent(TimelineId TimelineId) : IDomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using BuildingBlocks.Domain.Abstractions;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

[JsonConverter(typeof(TimelineIdJsonConverter))]
public class TimelineId : StronglyTypedId
{
private TimelineId(Guid value) : base(value) { }

public static TimelineId Of(Guid value) => new(value);

public override string ToString() => Value.ToString();

public override bool Equals(object? obj)
{
return obj is TimelineId other && Value == other.Value;
}

public override int GetHashCode()
{
return Value.GetHashCode();
}
}

public class TimelineIdValueConverter : ValueConverter<TimelineId, Guid>
{
public TimelineIdValueConverter()
: base(
timelineId => timelineId.Value, // Convert from NodeId to Guid
guid => TimelineId.Of(guid)) // Convert from Guid to NodeId
{
}
}

public class TimelineIdJsonConverter : JsonConverter<TimelineId>
{
public override TimelineId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (reader.TokenType)
{
case JsonTokenType.String:
{
var guidString = reader.GetString();
if (!Guid.TryParse(guidString, out var guid))
throw new JsonException($"Invalid GUID format for TimelineId: {guidString}");

return TimelineId.Of(guid);
}
case JsonTokenType.StartObject:
{
using var jsonDoc = JsonDocument.ParseValue(ref reader);

if (!jsonDoc.RootElement.TryGetProperty("id", out JsonElement idElement))
throw new JsonException("Expected property 'id' not found.");

var guidString = idElement.GetString();

if (!Guid.TryParse(guidString, out var guid))
throw new JsonException($"Invalid GUID format for TimelineId: {guidString}");

return TimelineId.Of(guid);
}
default:
throw new JsonException(
$"Unexpected token parsing TimelineId. Expected String or StartObject, got {reader.TokenType}.");
}
}

public override void Write(Utf8JsonWriter writer, TimelineId value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.DependencyInjection;
global using BuildingBlocks.Domain.Abstractions;
global using BuildingBlocks.Domain.ValueObjects.Ids;
global using Files.Domain.Models;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using BuildingBlocks.Domain.Nodes.Node.Dtos;
using System;
using System.Collections.Generic;
using BuildingBlocks.Domain.Nodes.Node.ValueObjects;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;
using Nodes.Application.Entities.Nodes.Commands.CreateNode;

// ReSharper disable ClassNeverInstantiated.Global
Expand Down Expand Up @@ -30,11 +32,14 @@ public void AddRoutes(IEndpointRouteBuilder app)

public class CreateNodeRequest
{
public CreateNodeRequest() { }

public CreateNodeRequest(NodeDto node) => Node = node;

public NodeDto Node { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Timestamp { get; set; }
public int Importance { get; set; }
public string Phase { get; set; }
public List<string> Categories { get; set; }
public List<string> Tags { get; set; }
public TimelineId TimelineId { get; set; }
}

public record CreateNodeResponse(NodeId Id);
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapGet("/Nodes/{nodeId}", async (string nodeId, ISender sender) =>
{
var result = await sender.Send(new GetNodeByIdQuery(nodeId));
var response = result.Adapt<GetNodeByIdResponse>();
var result = await sender.Send(new GetNodeByIdQuery(nodeId));
var response = new GetNodeByIdResponse(result.NodeDto);

return Results.Ok(response);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public void AddRoutes(IEndpointRouteBuilder app)
app.MapGet("/Nodes", async ([AsParameters] PaginationRequest query, ISender sender) =>
{
var result = await sender.Send(new ListNodesQuery(query));
var response = result.Adapt<ListNodesResponse>();
var response = new ListNodesResponse(result.Nodes);

return Results.Ok(response);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using BuildingBlocks.Domain.Nodes.Node.Dtos;
using BuildingBlocks.Domain.Nodes.Node.ValueObjects;
using BuildingBlocks.Domain.Reminders.Reminder.ValueObjects;
using BuildingBlocks.Domain.Timelines.Timeline.Dtos;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using Nodes.Application.Data.Abstractions;
Expand All @@ -14,7 +15,11 @@ public class NodesService(INodesRepository nodesRepository, IServiceProvider ser
public async Task<NodeDto> GetNodeByIdAsync(NodeId nodeId, CancellationToken cancellationToken)
{
var node = await nodesRepository.GetNodeByIdAsync(nodeId, cancellationToken);
var nodeDto = node.ToNodeDto();

var timelineService = serviceProvider.GetRequiredService<ITimelinesService>();
var timeline = await timelineService.GetTimelineBaseDtoAsync(node.TimelineId, cancellationToken);

var nodeDto = node.ToNodeDto(timeline);

var remindersService = serviceProvider.GetRequiredService<IRemindersService>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
using BuildingBlocks.Domain.Nodes.Node.Dtos;
using BuildingBlocks.Domain.Nodes.Node.ValueObjects;
using BuildingBlocks.Domain.Timelines.Timeline.ValueObjects;

namespace Nodes.Application.Entities.Nodes.Commands.CreateNode;

// ReSharper disable once ClassNeverInstantiated.Global
public record CreateNodeCommand(NodeDto Node) : ICommand<CreateNodeResult>;
public record CreateNodeCommand : ICommand<CreateNodeResult>
{
public required string Title { get; set; }
public required string Description { get; set; }
public required string Phase { get; set; }
public required DateTime Timestamp { get; set; }
public required int Importance { get; set; }
public required List<string> Categories { get; set; }
public required List<string> Tags { get; set; }
public required TimelineId TimelineId { get; set; }
}

// ReSharper disable once NotAccessedPositionalProperty.Global
public record CreateNodeResult(NodeId Id);
Expand All @@ -13,40 +23,40 @@ public class CreateNodeCommandValidator : AbstractValidator<CreateNodeCommand>
{
public CreateNodeCommandValidator()
{
RuleFor(x => x.Node.Title)
RuleFor(x => x.Title)
.NotEmpty().WithMessage("Title is required.")
.MaximumLength(100).WithMessage("Title must not exceed 100 characters.");

RuleFor(x => x.Node.Description)
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Description is required.")
.MaximumLength(500).WithMessage("Description must not exceed 500 characters.");

RuleFor(x => x.Node.Timestamp)
RuleFor(x => x.Timestamp)
.LessThanOrEqualTo(DateTime.Now).WithMessage("Timestamp cannot be in the future.");

RuleFor(x => x.Node.Importance)
RuleFor(x => x.Importance)
.InclusiveBetween(1, 10).WithMessage("Importance must be between 1 and 10.");

RuleFor(x => x.Node.Phase)
RuleFor(x => x.Phase)
.NotEmpty().WithMessage("Phase is required.");

RuleFor(x => x.Node)
RuleFor(x => x)
.NotNull().WithMessage("Node cannot be null.")
.DependentRules(() =>
{
RuleFor(x => x.Node.Categories)
RuleFor(x => x.Categories)
.Must(categories => categories != null && categories.Count > 0)
.WithMessage("At least one category must be provided.");

RuleFor(x => x.Node.Tags)
RuleFor(x => x.Tags)
.Must(tags => tags != null && tags.Count > 0)
.WithMessage("At least one tag must be provided.");
});

RuleForEach(x => x.Node.Categories)
RuleForEach(x => x.Categories)
.MaximumLength(50).WithMessage("Category must not exceed 50 characters.");

RuleForEach(x => x.Node.Tags)
RuleForEach(x => x.Tags)
.MaximumLength(50).WithMessage("Tag must not exceed 50 characters.");
}
}
Loading