diff --git a/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj b/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj
new file mode 100644
index 0000000..1e16afc
--- /dev/null
+++ b/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs b/AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs
similarity index 90%
rename from AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs
rename to AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs
index d2cc9c9..fae6253 100644
--- a/AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs
@@ -1,6 +1,6 @@
using AdvancedTodoList.Core.Models.Auth;
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// Represents a minimal view DTO for
diff --git a/AdvancedTodoList.Core/Dtos/AuthDtos.cs b/AdvancedTodoList.Application/Dtos/AuthDtos.cs
similarity index 93%
rename from AdvancedTodoList.Core/Dtos/AuthDtos.cs
rename to AdvancedTodoList.Application/Dtos/AuthDtos.cs
index 7938d05..1d175e7 100644
--- a/AdvancedTodoList.Core/Dtos/AuthDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/AuthDtos.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// DTO for log in.
diff --git a/AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs b/AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs
similarity index 75%
rename from AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs
rename to AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs
index 5b2a4ac..b556146 100644
--- a/AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// Represents a DTO for invitation link view.
diff --git a/AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs b/AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs
similarity index 88%
rename from AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs
rename to AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs
index f2e52ad..020249b 100644
--- a/AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs
+++ b/AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs
@@ -1,6 +1,6 @@
using AdvancedTodoList.Core.Models.TodoLists.Members;
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// Represents an aggregate of a to-do list member with a role.
diff --git a/AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs b/AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs
similarity index 70%
rename from AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs
rename to AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs
index 76f079d..efc60d6 100644
--- a/AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
public record TodoItemCategoryCreateDto(string Name);
diff --git a/AdvancedTodoList.Core/Dtos/TodoListDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListDtos.cs
similarity index 90%
rename from AdvancedTodoList.Core/Dtos/TodoListDtos.cs
rename to AdvancedTodoList.Application/Dtos/TodoListDtos.cs
index 6bcbb1a..6c5e62f 100644
--- a/AdvancedTodoList.Core/Dtos/TodoListDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/TodoListDtos.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// DTO for creating/editing a to-do list.
diff --git a/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs
new file mode 100644
index 0000000..d904e0e
--- /dev/null
+++ b/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs
@@ -0,0 +1,37 @@
+using AdvancedTodoList.Core.Models.TodoLists;
+
+namespace AdvancedTodoList.Application.Dtos;
+
+///
+/// DTO for creating/editing a to-do list item.
+///
+public record TodoItemCreateDto(
+ string Name, string Description, DateTime? DeadlineDate,
+ int Priority, int? CategoryId
+ );
+
+///
+/// DTO for changing the state of a to-do list item.
+///
+public record TodoItemUpdateStateDto(TodoItemState State);
+
+///
+/// DTO for a full view of a to-do list item.
+///
+public record TodoItemGetByIdDto(
+ int Id, string TodoListId, string Name,
+ string Description, DateTime? DeadlineDate,
+ TodoItemState State, int Priority,
+ ApplicationUserPreviewDto Owner,
+ TodoItemCategoryViewDto? Category
+ );
+
+///
+/// DTO for a partial view of a to-do list item.
+///
+public record TodoItemPreviewDto(
+ int Id, string TodoListId, string Name,
+ DateTime? DeadlineDate, TodoItemState State,
+ int Priority, ApplicationUserPreviewDto Owner,
+ TodoItemCategoryViewDto? Category
+ );
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs
similarity index 92%
rename from AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs
rename to AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs
index 4179c78..000dbe6 100644
--- a/AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// DTO for a minimal view of a to-do list member.
diff --git a/AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs
similarity index 91%
rename from AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs
rename to AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs
index e20c8e4..f6eca62 100644
--- a/AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs
+++ b/AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs
@@ -1,6 +1,6 @@
using AdvancedTodoList.Core.Models.TodoLists.Members;
-namespace AdvancedTodoList.Core.Dtos;
+namespace AdvancedTodoList.Application.Dtos;
///
/// Represents a partial view for a role.
diff --git a/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs b/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs
new file mode 100644
index 0000000..5175ad8
--- /dev/null
+++ b/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs
@@ -0,0 +1,30 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using Mapster;
+
+namespace AdvancedTodoList.Application.Mapping;
+
+///
+/// Class that defines global mapping settings.
+///
+public static class MappingGlobalSettings
+{
+ ///
+ /// Apply global mapping settings.
+ ///
+ public static void Apply()
+ {
+ // Ignore null IDs
+ TypeAdapterConfig.NewConfig()
+ .IgnoreIf((src, dest) => src.RoleId == null, dest => dest.Role!);
+ TypeAdapterConfig.NewConfig()
+ .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!);
+ TypeAdapterConfig.NewConfig()
+ .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!);
+
+ // Convert null strings into empty strings and trim strings
+ TypeAdapterConfig.GlobalSettings.Default
+ .AddDestinationTransform((string? dest) => dest != null ? dest.Trim() : string.Empty);
+ }
+}
diff --git a/AdvancedTodoList.Application/Options/AccessTokenOptions.cs b/AdvancedTodoList.Application/Options/AccessTokenOptions.cs
new file mode 100644
index 0000000..185ae54
--- /dev/null
+++ b/AdvancedTodoList.Application/Options/AccessTokenOptions.cs
@@ -0,0 +1,27 @@
+namespace AdvancedTodoList.Application.Options;
+
+///
+/// A class that contains access token options.
+///
+public class AccessTokenOptions
+{
+ ///
+ /// Valid audience of tokens.
+ ///
+ public string ValidAudience { get; set; } = null!;
+
+ ///
+ /// Valid issuer of tokens.
+ ///
+ public string ValidIssuer { get; set; } = null!;
+
+ ///
+ /// Seconds before token expires.
+ ///
+ public int ExpirationSeconds { get; set; }
+
+ ///
+ /// A secret key used for signing access tokens.
+ ///
+ public string SecretKey { get; set; } = null!;
+}
diff --git a/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs b/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs
new file mode 100644
index 0000000..2430e7e
--- /dev/null
+++ b/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs
@@ -0,0 +1,17 @@
+namespace AdvancedTodoList.Application.Options;
+
+///
+/// A class that contains invitation link options.
+///
+public class InvitationLinkOptions
+{
+ ///
+ /// Size of the refresh token in bytes.
+ ///
+ public int Size { get; set; }
+
+ ///
+ /// Days before token expires.
+ ///
+ public int ExpirationDays { get; set; }
+}
diff --git a/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs b/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs
new file mode 100644
index 0000000..1877057
--- /dev/null
+++ b/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs
@@ -0,0 +1,17 @@
+namespace AdvancedTodoList.Application.Options;
+
+///
+/// A class that contains refresh token options.
+///
+public class RefreshTokenOptions
+{
+ ///
+ /// Size of the refresh token in bytes.
+ ///
+ public int Size { get; set; }
+
+ ///
+ /// Days before token expires.
+ ///
+ public int ExpirationDays { get; set; }
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs
new file mode 100644
index 0000000..bf8385a
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs
@@ -0,0 +1,28 @@
+using AdvancedTodoList.Core.Models.Auth;
+
+namespace AdvancedTodoList.Application.Services.Definitions.Auth;
+
+///
+/// An interface for a service that manages access tokens.
+///
+public interface IAccessTokensService
+{
+ ///
+ /// Generates an access token for the user.
+ ///
+ /// User which will receive an access token.
+ ///
+ /// A string that represents an access token.
+ ///
+ string GenerateAccessToken(ApplicationUser user);
+
+ ///
+ /// Validates an access token without checking expiration time and then returns
+ /// ID of the user stored in it asynchronously.
+ ///
+ /// A string that represents an access token.
+ ///
+ /// A user ID retrieved from the access token or , if validation failed.
+ ///
+ Task GetUserIdFromExpiredTokenAsync(string accessToken);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs
new file mode 100644
index 0000000..eea50ea
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs
@@ -0,0 +1,49 @@
+using AdvancedTodoList.Application.Dtos;
+
+namespace AdvancedTodoList.Application.Services.Definitions.Auth;
+
+///
+/// Interface for service that performs authentication operations.
+///
+public interface IAuthService
+{
+ ///
+ /// Logs a user in asynchronously.
+ ///
+ /// Data required for logging in.
+ ///
+ /// Returns a task representing the asynchronous operation, containing a
+ /// response with access and refresh tokens or null if authorization fails.
+ ///
+ Task LogInAsync(LogInDto logInDto);
+
+ ///
+ /// Registers a new user asynchronously.
+ ///
+ /// Data required for user registration.
+ ///
+ /// Returns a task representing the asynchronous operation, containing the registration result.
+ ///
+ Task RegisterAsync(RegisterDto registerDto);
+
+ ///
+ /// Refreshes the access token asynchronously.
+ ///
+ /// Data required for token refresh.
+ ///
+ /// Returns a task representing the asynchronous operation,
+ /// containing a response with access and refresh tokens or null if authorization fails.
+ ///
+ Task RefreshAsync(RefreshDto refreshDto);
+
+ ///
+ /// Logs a user out asynchronously by revoking a refresh token.
+ ///
+ /// ID of the caller.
+ /// Data required for logging out.
+ ///
+ /// Returns a task representing the asynchronous operation,
+ /// indicating the success or failure of the operation.
+ ///
+ Task LogOutAsync(string userId, LogOutDto logOutDto);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs
new file mode 100644
index 0000000..8fb10bb
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs
@@ -0,0 +1,68 @@
+using AdvancedTodoList.Core.Models;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+
+namespace AdvancedTodoList.Application.Services.Definitions.Auth;
+
+///
+/// Interface for a service that checks user's permissions.
+///
+public interface IPermissionsChecker
+{
+ ///
+ /// Asynchronously checks whether the user is a member of the to-do list with
+ /// specified ID.
+ ///
+ /// To-do list context.
+ ///
+ /// if user is a member of the list; otherwise .
+ ///
+ Task IsMemberOfListAsync(TodoListContext context);
+
+ ///
+ /// Asynchronously checks whether the user is a member of the to-do list and
+ /// has a permission defined by the funciton .
+ ///
+ /// To-do list context.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user is a member of the list and has required permission;
+ /// otherwise .
+ ///
+ Task HasPermissionAsync(TodoListContext context, Func permission);
+
+ ///
+ /// Asynchronously checks whether the user can touch an entity.
+ ///
+ ///
+ /// This method firstly checks whether implements
+ /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list;
+ /// otherwise the method checks if user has the permission defined by the function .
+ ///
+ /// Type of the entity.
+ /// Type of the unique identifier used by the entity.
+ /// To-do list context.
+ /// ID of the entity.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user is either an owner of the entity and a member of a to-do list,
+ /// or he/she/they has permission defined by ; otherwise .
+ ///
+ Task CanTouchEntityAsync(TodoListContext context, TEntity entity,
+ Func permission)
+ where TEntity : class, IEntity
+ where TKey : IEquatable;
+
+ ///
+ /// Asynchronously checks whether the user has a permission to change the role
+ /// with the priority of .
+ ///
+ /// To-do list context.
+ /// ID of the role.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user has and highest role priority than
+ /// the ; otherwise .
+ ///
+ Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority,
+ Func permission);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs
new file mode 100644
index 0000000..a43f2c7
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs
@@ -0,0 +1,37 @@
+namespace AdvancedTodoList.Application.Services.Definitions.Auth;
+
+///
+/// An interface for a service that manages refresh tokens.
+///
+public interface IRefreshTokensService
+{
+ ///
+ /// Generates a refresh token for the user and saves it asynchronously.
+ ///
+ /// ID of the user who will receive the token.
+ ///
+ /// A string that represents a refresh token or if user does not exist.
+ ///
+ Task GenerateAsync(string userId);
+
+ ///
+ /// Revokes the refresh token of the user asynchronously
+ ///
+ /// ID of the user whose token is being revoked.
+ /// Value of the token to be revoked.
+ ///
+ /// on success; otherwise.
+ ///
+ Task RevokeAsync(string userId, string token);
+
+ ///
+ /// Checks whether refresh token is valid asynchronously.
+ ///
+ /// ID of the user whose token is being validated.
+ /// Value of the token to be validated.
+ ///
+ /// if token is valid;
+ /// otherwise.
+ ///
+ Task ValidateAsync(string userId, string token);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs
new file mode 100644
index 0000000..c9d9e7e
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs
@@ -0,0 +1,46 @@
+namespace AdvancedTodoList.Application.Services.Definitions.Auth;
+
+///
+/// Class that represents a result of register operation.
+///
+public class RegisterResult
+{
+ ///
+ /// Flag which determines whether result reports succeess.
+ ///
+ public bool IsSuccess { get; private init; }
+ ///
+ /// Error messages.
+ ///
+ public IEnumerable Errors { get; private init; }
+
+ private RegisterResult(bool isSuccess, IEnumerable errors)
+ {
+ IsSuccess = isSuccess;
+ Errors = errors;
+ }
+
+ ///
+ /// Returns a result that reports success.
+ ///
+ ///
+ /// A result that reports success.
+ ///
+ public static RegisterResult Success() => new(true, []);
+
+ ///
+ /// Returns a result that reports failure.
+ ///
+ /// Error messages.
+ ///
+ /// A result that reports failure.
+ ///
+ public static RegisterResult Failure(IEnumerable errors) => new(false, errors);
+}
+
+///
+/// Record that represents a register error.
+///
+/// Property that caused an error or '$' if error was not caused by a property.
+/// An error message.
+public record RegisterError(string Property, string Message);
diff --git a/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs b/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs
new file mode 100644
index 0000000..f335ac5
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs
@@ -0,0 +1,54 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// An interface for a service that manages invitation links.
+///
+public interface IInvitationLinksService
+{
+ ///
+ /// Joins the caller to the to-do list by invitation list asynchronously.
+ ///
+ /// ID of the caller.
+ /// Invitation link to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ Task JoinAsync(string callerId, string invitationLinkValue);
+
+ ///
+ /// Gets invitation links associated with the to-do list asynchronously.
+ ///
+ /// To-do list context of the operation.
+ /// Pagination parameters to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ Task>> GetInvitationLinksAsync(TodoListContext context,
+ PaginationParameters parameters);
+
+ ///
+ /// Creates an invitation link associated to the to-do list asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ Task> CreateAsync(TodoListContext context);
+
+ ///
+ /// Deletes an invitation link associted to the to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the link.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ Task DeleteAsync(TodoListContext context, int linkId);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs
new file mode 100644
index 0000000..55cae08
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs
@@ -0,0 +1,78 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Interface for a service that manages to-do list items categories.
+///
+public interface ITodoItemCategoriesService
+{
+ ///
+ /// Asynchronously checks whether the category ID is valid for the given context.
+ ///
+ ///
+ /// ID is considered as valid.
+ ///
+ /// To-do list context.
+ /// ID of the category to validate.
+ ///
+ /// A task representing the asynchronous operation.
+ /// if ID is valid, otherwise.
+ ///
+ public Task IsCategoryValidForContext(TodoListContext context, int? categoryId);
+
+ ///
+ /// Retrieves a page of to-do list items categories of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Optional name to filter categories by.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetCategoriesOfListAsync(
+ TodoListContext context, PaginationParameters paginationParameters, string? name = null);
+
+ ///
+ /// Retrieves a to-do list item category by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context, int categoryId);
+
+ ///
+ /// Creates a new to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list item.
+ /// ID of the user who creates the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto);
+
+ ///
+ /// Edits a to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to edit.
+ /// The DTO containing information for editing the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto);
+
+ ///
+ /// Deletes a to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task DeleteAsync(TodoListContext context, int categoryId);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs
new file mode 100644
index 0000000..810d229
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs
@@ -0,0 +1,75 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Specifications.Filters;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Interface for a service that manages to-do list items.
+///
+public interface ITodoItemsService
+{
+ ///
+ /// Retrieves a page of to-do list items of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Filter parameters to apply.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetItemsOfListAsync(
+ TodoListContext context, PaginationParameters paginationParameters, TodoItemsFilter filter);
+
+ ///
+ /// Retrieves a to-do list item by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context, int itemId);
+
+ ///
+ /// Creates a new to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task CreateAsync(TodoListContext context, TodoItemCreateDto dto);
+
+ ///
+ /// Edits a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to edit.
+ /// The DTO containing information for editing the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto);
+
+ ///
+ /// Updates the state of a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to update the state.
+ /// The DTO which contains the state of the to-do item to set.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto);
+
+ ///
+ /// Deletes a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task DeleteAsync(TodoListContext context, int itemId);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs
new file mode 100644
index 0000000..30a371e
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs
@@ -0,0 +1,94 @@
+using AdvancedTodoList.Core.Models;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Specifications;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// An interface that represents a service for perfoming CRUD operations
+/// on to-do list dependant entities and mapping/unmapping DTOs.
+///
+/// Type of the to-do list dependant entity.
+/// Type of the unique identifier used by the entity.
+public interface ITodoListDependantEntitiesService
+ where TEntity : class, IEntity, ITodoListDependant
+ where TKey : IEquatable
+{
+ ///
+ /// Retrieves a page of to-do list dependant entities mapped to .
+ ///
+ ///
+ /// This method checks if to-do list ID is valid, but doesn't filter by it.
+ /// Filtering should be done in .
+ ///
+ /// To-do list context.
+ /// Specification to apply to entities.
+ /// Pagination parameters to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the requested page on success.
+ ///
+ public Task>> GetPageAsync(TodoListContext context,
+ ISpecification specification, PaginationParameters paginationParameters);
+
+ ///
+ /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to .
+ ///
+ /// To-do list context.
+ /// The ID of the entity to retrieve.
+ /// DTO to map entity to.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the requested dto on success.
+ ///
+ Task> GetByIdAsync(TodoListContext context, TKey entityId) where TDto : class;
+
+ ///
+ /// Creates a new a to-do list dependant entity asynchronously from the DTO.
+ ///
+ ///
+ /// If implements the interface, then
+ /// this method will set the caller as an owner.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the entity.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the created mapped to
+ /// on success.
+ ///
+ public Task> CreateAsync(
+ TodoListContext context, TInputDto dto, Func? permission = null)
+ where TOutputDto : class;
+
+ ///
+ /// Edits a to-do list dependant entity asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the entity to edit.
+ /// The DTO containing information for editing the entity.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public Task UpdateAsync(TodoListContext context, TKey entityId,
+ TDto dto, Func? permission = null);
+
+ ///
+ /// Deletes a to-do list dependant entity asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the entity to delete.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public Task DeleteAsync(TodoListContext context,
+ TKey entityId, Func? permission = null);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs
new file mode 100644
index 0000000..5887d72
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs
@@ -0,0 +1,55 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Specifications.Filters;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Interface for a service that manages to-do list members.
+///
+public interface ITodoListMembersService
+{
+ ///
+ /// Gets a page with members of a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Filter parameters to use.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ Task>> GetMembersAsync(TodoListContext context,
+ PaginationParameters paginationParameters, TodoListMembersFilter filter);
+
+ ///
+ /// Adds a member to a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// DTO that contains information needed for adding a member. Supossed to be valid.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto);
+
+ ///
+ /// Updates a role of the member of a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the member.
+ /// DTO that contains information needed for updating a role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ Task UpdateMemberRoleAsync(TodoListContext context,
+ int memberId, TodoListMemberUpdateRoleDto dto);
+
+ ///
+ /// Removes a member from a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the member.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ Task RemoveMemberAsync(TodoListContext context, int memberId);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs
new file mode 100644
index 0000000..81794e1
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs
@@ -0,0 +1,63 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Interface for a service that manages to-do list role.
+///
+public interface ITodoListRolesService
+{
+ ///
+ /// Retrieves a page of to-do list roles of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Optional name to filter categories by.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetRolesOfListAsync(
+ TodoListContext context, PaginationParameters paginationParameters, string? name = null);
+
+ ///
+ /// Retrieves a to-do list role by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context, int roleId);
+
+ ///
+ /// Creates a new to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto);
+
+ ///
+ /// Edits a to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to edit.
+ /// The DTO containing information for editing the to-do list role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto);
+
+ ///
+ /// Deletes a to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task DeleteAsync(TodoListContext context, int roleId);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs
new file mode 100644
index 0000000..bf0b941
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs
@@ -0,0 +1,69 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Specifications.Filters;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Interface for a service that manages to-do lists.
+///
+public interface ITodoListsService
+{
+ ///
+ /// Retrieves a page of to-do lists, with the requirement that the user
+ /// is a member of those lists.
+ ///
+ /// Id of the user
+ /// Pagination parameters to use.
+ /// Filter parameters to apply.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetListsOfUserAsync(string userId,
+ PaginationParameters paginationParameters, TodoListsFilter filter);
+
+ ///
+ /// Retrieves a to-do list by its ID asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context);
+
+ ///
+ /// Creates a new to-do list asynchronously.
+ ///
+ ///
+ /// This method should also create an "Owner" role with all permissions and assign the caller to it.
+ ///
+ /// The DTO containing information for creating the to-do list.
+ /// ID of the user who creates the to-do list.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a created model mapped to .
+ ///
+ public Task CreateAsync(TodoListCreateDto dto, string callerId);
+
+ ///
+ /// Edits a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for editing the to-do list.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public Task EditAsync(TodoListContext context, TodoListCreateDto dto);
+
+ ///
+ /// Deletes a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public Task DeleteAsync(TodoListContext context);
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs b/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs
new file mode 100644
index 0000000..b0c9022
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs
@@ -0,0 +1,42 @@
+using AdvancedTodoList.Application.Dtos;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Represents possible results of the join to-do list by invitatation link operation.
+///
+public class JoinByInvitationLinkResult(JoinByInvitationLinkStatus status, TodoListMemberMinimalViewDto? dto = null)
+{
+ ///
+ /// Status of the operation.
+ ///
+ public JoinByInvitationLinkStatus Status { get; } = status;
+
+ ///
+ /// Gets additional DTO of the member, can be .
+ ///
+ public TodoListMemberMinimalViewDto? Dto { get; } = dto;
+}
+
+///
+/// Enum that represents possible result statuses of the join to-do list by invitatation link operation.
+///
+public enum JoinByInvitationLinkStatus
+{
+ ///
+ /// Operation was successfull.
+ ///
+ Success,
+ ///
+ /// Invitation link was not found.
+ ///
+ NotFound,
+ ///
+ /// Invitation link is expired.
+ ///
+ Expired,
+ ///
+ /// User is already a member of the to-do list.
+ ///
+ UserIsAlreadyMember
+}
\ No newline at end of file
diff --git a/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs
new file mode 100644
index 0000000..05cc5dd
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs
@@ -0,0 +1,39 @@
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Represent a generic service response which indicates an operation status and
+/// contains an additonal information.
+///
+/// Type of the content saved in the response.
+public class ServiceResponse(ServiceResponseStatus status, T? result = default)
+{
+ ///
+ /// Gets the status of operation.
+ ///
+ public ServiceResponseStatus Status { get; } = status;
+
+ ///
+ /// Gets the result of the operation.
+ ///
+ public T? Result { get; } = result;
+}
+
+///
+/// Represents a possible service response status.
+///
+public enum ServiceResponseStatus
+{
+ ///
+ /// Status that indicates success.
+ ///
+ Success,
+ ///
+ /// Status that indicates that entity could not be found or the caller isn't
+ /// suppossed to know that entity exists.
+ ///
+ NotFound,
+ ///
+ /// Status that indicates that user has no permission to perform the operation.
+ ///
+ Forbidden
+}
diff --git a/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs
new file mode 100644
index 0000000..51701bc
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs
@@ -0,0 +1,37 @@
+using AdvancedTodoList.Application.Dtos;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+public class TodoItemsServiceResponse(TodoItemsServiceStatus status, TodoItemGetByIdDto? result = null)
+{
+ ///
+ /// Status of the operation.
+ ///
+ public TodoItemsServiceStatus Status { get; set; } = status;
+
+ ///
+ /// Result of the operation.
+ ///
+ public TodoItemGetByIdDto? Result { get; set; } = result;
+}
+
+public enum TodoItemsServiceStatus
+{
+ ///
+ /// Status that indicates success.
+ ///
+ Success,
+ ///
+ /// Status that indicates that entity could not be found or the caller isn't
+ /// suppossed to know that entity exists.
+ ///
+ NotFound,
+ ///
+ /// Status that indicates that user has no permission to perform the operation.
+ ///
+ Forbidden,
+ ///
+ /// Status that indicates that user provided an invalid category ID.
+ ///
+ InvalidCategoryId
+}
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Services/TodoListContext.cs b/AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs
similarity index 81%
rename from AdvancedTodoList.Core/Services/TodoListContext.cs
rename to AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs
index b7188ef..1657806 100644
--- a/AdvancedTodoList.Core/Services/TodoListContext.cs
+++ b/AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Services;
+namespace AdvancedTodoList.Application.Services.Definitions;
///
/// Represents a context of an operation related to the to-do list.
diff --git a/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs
new file mode 100644
index 0000000..f1a6a53
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs
@@ -0,0 +1,47 @@
+using AdvancedTodoList.Application.Dtos;
+
+namespace AdvancedTodoList.Application.Services.Definitions;
+
+///
+/// Class that represents possible results of the method .
+///
+public class AddTodoListMemberServiceResult(
+ TodoListMemberServiceResultStatus status, TodoListMemberMinimalViewDto? dto = null)
+{
+ ///
+ /// Status of the operation.
+ ///
+ public TodoListMemberServiceResultStatus Status { get; } = status;
+
+ ///
+ /// Gets additional DTO of the member, can be .
+ ///
+ public TodoListMemberMinimalViewDto? Dto { get; } = dto;
+}
+
+///
+/// Enum that represents possible result statuses of the to-do list members service.
+///
+public enum TodoListMemberServiceResultStatus
+{
+ ///
+ /// Operation was successfull.
+ ///
+ Success,
+ ///
+ /// To-do list was not found.
+ ///
+ NotFound,
+ ///
+ /// User is already a member of the to-do list, returned only in the add member method.
+ ///
+ UserAlreadyAdded,
+ ///
+ /// Role either doesn't exist or it's invalid for the current to-do list.
+ ///
+ InvalidRoleId,
+ ///
+ /// User has no permission to perform the operation.
+ ///
+ Forbidden
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs
new file mode 100644
index 0000000..2c78cc2
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs
@@ -0,0 +1,83 @@
+using AdvancedTodoList.Application.Options;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.Auth;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+
+namespace AdvancedTodoList.Application.Services.Implementations.Auth;
+
+///
+/// A service that manages access tokens.
+///
+public class AccessTokensService(IOptions options) : IAccessTokensService
+{
+ private readonly AccessTokenOptions _tokenOptions = options.Value;
+
+ ///
+ /// Generates an access token for the user.
+ ///
+ /// User which will receive an access token.
+ ///
+ /// A string that represents an access token.
+ ///
+ public string GenerateAccessToken(ApplicationUser user)
+ {
+ SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_tokenOptions.SecretKey));
+
+ List claims =
+ [
+ new(JwtRegisteredClaimNames.Sub, user.Id),
+ new(JwtRegisteredClaimNames.Email, user.Email!),
+ new(JwtRegisteredClaimNames.UniqueName, user.UserName!),
+ new(JwtRegisteredClaimNames.GivenName, user.FirstName),
+ new(JwtRegisteredClaimNames.FamilyName, user.LastName),
+ ];
+
+ JwtSecurityToken token = new(
+ issuer: _tokenOptions.ValidIssuer,
+ audience: _tokenOptions.ValidAudience,
+ expires: DateTime.UtcNow.AddSeconds(_tokenOptions.ExpirationSeconds),
+ claims: claims,
+ signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
+ );
+ string accessToken = new JwtSecurityTokenHandler().WriteToken(token);
+
+ return accessToken;
+ }
+
+ ///
+ /// Validates an access token without checking expiration time and then returns
+ /// ID of the user stored in it asynchronously.
+ ///
+ /// A string that represents an access token.
+ ///
+ /// A user ID retrieved from the access token or , if validation failed.
+ ///
+ public async Task GetUserIdFromExpiredTokenAsync(string accessToken)
+ {
+ // Validate the access token
+ string key = _tokenOptions.SecretKey;
+ JwtSecurityTokenHandler tokenHandler = new();
+ TokenValidationParameters validationParameters = new()
+ {
+ ValidateLifetime = false, // Ignore expiration time
+ ValidIssuer = _tokenOptions.ValidIssuer,
+ ValidAudience = _tokenOptions.ValidAudience,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
+ };
+ var validationResult = await tokenHandler.ValidateTokenAsync(
+ accessToken, validationParameters);
+ // Return null if validation failed
+ if (!validationResult.IsValid) return null;
+
+ // Get ID of the user
+ JwtSecurityToken jwtToken = (JwtSecurityToken)validationResult.SecurityToken;
+ var subClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub);
+ if (subClaim == null) return null;
+
+ return subClaim.Value;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs
new file mode 100644
index 0000000..312e0f9
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs
@@ -0,0 +1,156 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.Auth;
+using Microsoft.AspNetCore.Identity;
+
+namespace AdvancedTodoList.Application.Services.Implementations.Auth;
+
+///
+/// Service that performs authentication operations.
+///
+public class AuthService(
+ IAccessTokensService accessTokensService,
+ IRefreshTokensService refreshTokensService,
+ UserManager userManager)
+ : IAuthService
+{
+ private readonly IAccessTokensService _accessTokensService = accessTokensService;
+ private readonly IRefreshTokensService _refreshTokensService = refreshTokensService;
+ private readonly UserManager _userManager = userManager;
+
+ ///
+ /// Logs a user in asynchronously.
+ ///
+ /// Data required for logging in.
+ ///
+ /// Returns a task representing the asynchronous operation, containing a
+ /// response with access and refresh tokens or null if authorization fails.
+ ///
+ public async Task LogInAsync(LogInDto logInDto)
+ {
+ // Try to find a user
+ ApplicationUser? user;
+ // Try to find by email
+ if (logInDto.UserNameOrEmail.Contains('@'))
+ {
+ user = await _userManager.FindByEmailAsync(logInDto.UserNameOrEmail);
+ }
+ // Try to find by username
+ else
+ {
+ user = await _userManager.FindByNameAsync(logInDto.UserNameOrEmail);
+ }
+
+ // Invalid username/email - fail
+ if (user == null) return null;
+
+ // Invalid password - fail
+ if (!await _userManager.CheckPasswordAsync(user, logInDto.Password))
+ return null;
+
+ // Generate a new refresh token for the user
+ string? refreshToken = await _refreshTokensService.GenerateAsync(user.Id);
+ if (refreshToken == null) return null;
+
+ // Generate an access token
+ string accessToken = _accessTokensService.GenerateAccessToken(user);
+
+ // Return both tokens
+ return new(accessToken, refreshToken);
+ }
+
+ ///
+ /// Logs a user out asynchronously by revoking a refresh token.
+ ///
+ /// ID of the caller.
+ /// Data required for logging out.
+ ///
+ /// Returns a task representing the asynchronous operation,
+ /// indicating the success or failure of the operation.
+ ///
+ public async Task LogOutAsync(string userId, LogOutDto logOutDto)
+ {
+ // Revoke the token
+ return await _refreshTokensService.RevokeAsync(userId, logOutDto.RefreshToken);
+ }
+
+ ///
+ /// Refreshes the access token asynchronously.
+ ///
+ /// Data required for token refresh.
+ ///
+ /// Returns a task representing the asynchronous operation,
+ /// containing a response with access and refresh tokens or null if authorization fails.
+ ///
+ public async Task RefreshAsync(RefreshDto refreshDto)
+ {
+ // Try to get a user ID
+ string? userId = await _accessTokensService
+ .GetUserIdFromExpiredTokenAsync(refreshDto.AccessToken);
+ if (userId == null) return null;
+
+ // Validate the refresh token
+ if (!await _refreshTokensService.ValidateAsync(userId, refreshDto.RefreshToken))
+ return null;
+
+ // Find the user
+ ApplicationUser? user = await _userManager.FindByIdAsync(userId);
+ // User doesn't exist - return null
+ if (user == null) return null;
+
+ // Generate access token
+ string accessToken = _accessTokensService.GenerateAccessToken(user);
+
+ // Return tokens
+ return new LogInResponse(accessToken, refreshDto.RefreshToken);
+ }
+
+ ///
+ /// Registers a new user asynchronously.
+ ///
+ /// Data required for user registration.
+ ///
+ /// Returns a task representing the asynchronous operation, containing the registration result.
+ ///
+ public async Task RegisterAsync(RegisterDto registerDto)
+ {
+ List errors = [];
+ // Check if email is available
+ if (await _userManager.FindByEmailAsync(registerDto.Email) != null)
+ errors.Add(new("Email", "Email is already taken."));
+ // Check if username is available
+ if (await _userManager.FindByNameAsync(registerDto.UserName) != null)
+ errors.Add(new("UserName", "Username is already taken."));
+
+ if (errors.Count > 0) return RegisterResult.Failure(errors);
+
+ // Try to register the user
+ ApplicationUser user = new()
+ {
+ FirstName = registerDto.FirstName,
+ LastName = registerDto.LastName,
+ Email = registerDto.Email,
+ UserName = registerDto.UserName
+ };
+ var result = await _userManager.CreateAsync(user, registerDto.Password);
+
+ // Return the result
+ return result.Succeeded ?
+ RegisterResult.Success() :
+ RegisterResult.Failure(IdentityErrorsToRegisterErrors(result.Errors));
+ }
+
+ private static IEnumerable IdentityErrorsToRegisterErrors(IEnumerable identityErrors)
+ {
+ foreach (var error in identityErrors)
+ {
+ // Determine the property which caused an error
+ string property = "$";
+ if (error.Code.Contains("Password")) property = "Password";
+ else if (error.Code.Contains("UserName")) property = "UserName";
+ else if (error.Code.Contains("Email")) property = "Email";
+
+ yield return new(property, error.Description);
+ }
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs
new file mode 100644
index 0000000..56dde25
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs
@@ -0,0 +1,102 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications;
+
+namespace AdvancedTodoList.Application.Services.Implementations.Auth;
+
+///
+/// Service that checks user's permissions.
+///
+public class PermissionsChecker(ITodoListMembersRepository membersRepository) : IPermissionsChecker
+{
+ private readonly ITodoListMembersRepository _membersRepository = membersRepository;
+
+ ///
+ /// Asynchronously checks whether the user is a member of the to-do list with
+ /// specified ID.
+ ///
+ /// To-do list context.
+ ///
+ /// if user is a member of the list; otherwise .
+ ///
+ public async Task IsMemberOfListAsync(TodoListContext context)
+ {
+ return await _membersRepository.FindAsync(context.TodoListId, context.CallerId) != null;
+ }
+
+ ///
+ /// Asynchronously checks whether the user is a member of the to-do list and
+ /// has a permission defined by the funciton .
+ ///
+ /// To-do list context.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user is a member of the list and has required permission;
+ /// otherwise .
+ ///
+ public async Task HasPermissionAsync(TodoListContext context, Func permission)
+ {
+ MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId);
+ var member = await _membersRepository.GetAggregateAsync(specification);
+ // User is not a member or has no role - return false
+ if (member == null || member.Role == null) return false;
+
+ return permission(member.Role.Permissions);
+ }
+
+ ///
+ /// Asynchronously checks whether the user can touch an entity.
+ ///
+ ///
+ /// This method firstly checks whether implements
+ /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list;
+ /// otherwise the method checks if user has the permission defined by the function .
+ ///
+ /// Type of the entity.
+ /// Type of the unique identifier used by the entity.
+ /// To-do list context.
+ /// ID of the entity.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user is either an owner of the entity and a member of a to-do list,
+ /// or he/she/they has permission defined by ; otherwise .
+ ///
+ Task IPermissionsChecker.CanTouchEntityAsync(TodoListContext context, TEntity entity, Func permission)
+ {
+ // If user owns entity only check if he/she/they is member
+ if (entity is IHasOwner ownedEntity && ownedEntity.OwnerId == context.CallerId)
+ {
+ return IsMemberOfListAsync(context);
+ }
+ // Otherwise check if user has permission
+ return HasPermissionAsync(context, permission);
+ }
+
+ ///
+ /// Asynchronously checks whether the user has a permission to change the role
+ /// with the priority of .
+ ///
+ /// To-do list context.
+ /// ID of the role.
+ /// Function that should return if user has required permission.
+ ///
+ /// if user has and highest role priority than
+ /// the ; otherwise .
+ ///
+ public async Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority, Func permission)
+ {
+ MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId);
+ var member = await _membersRepository.GetAggregateAsync(specification);
+
+ // User is not a member, has no role or permission - return false
+ if (member == null || member.Role == null || !permission(member.Role.Permissions))
+ return false;
+
+ // Check if user has a higher priority
+ return member.Role.Priority < rolePriority;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs
new file mode 100644
index 0000000..071800d
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs
@@ -0,0 +1,86 @@
+using AdvancedTodoList.Application.Options;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.Auth;
+using AdvancedTodoList.Core.Repositories;
+using Microsoft.Extensions.Options;
+using System.Security.Cryptography;
+
+namespace AdvancedTodoList.Application.Services.Implementations.Auth;
+
+public class RefreshTokensService(
+ IUserRefreshTokensRepository repository,
+ IOptions options,
+ IEntityExistenceChecker existenceChecker) : IRefreshTokensService
+{
+ private readonly IUserRefreshTokensRepository _repository = repository;
+ private readonly RefreshTokenOptions _tokenOptions = options.Value;
+ private readonly IEntityExistenceChecker _existenceChecker = existenceChecker;
+
+ ///
+ /// Generates a refresh token for the user and saves it asynchronously.
+ ///
+ /// ID of the user who will receive the token.
+ ///
+ /// A string that represents a refresh token or if user does not exist.
+ ///
+ public async Task GenerateAsync(string userId)
+ {
+ // Check if user exists
+ if (!await _existenceChecker.ExistsAsync(userId))
+ return null;
+
+ // Generate a token
+ using RandomNumberGenerator rng = RandomNumberGenerator.Create();
+ byte[] refreshTokenBytes = new byte[_tokenOptions.Size];
+ rng.GetBytes(refreshTokenBytes);
+
+ // Set the expiration date and assign token to the user
+ int expirationDays = _tokenOptions.ExpirationDays;
+ UserRefreshToken tokenEntity = new()
+ {
+ Token = Convert.ToBase64String(refreshTokenBytes),
+ UserId = userId,
+ ValidTo = DateTime.UtcNow.AddDays(expirationDays)
+ };
+
+ // Save the token
+ await _repository.AddAsync(tokenEntity);
+
+ // Return the token value
+ return tokenEntity.Token;
+ }
+
+ ///
+ /// Revokes the refresh token of the user asynchronously
+ ///
+ /// ID of the user whose token is being revoked.
+ /// Value of the token to be revoked.
+ ///
+ /// on success; otherwise.
+ ///
+ public async Task RevokeAsync(string userId, string token)
+ {
+ var tokenEntity = await _repository.FindAsync(userId, token);
+ if (tokenEntity == null) return false;
+
+ // Delete the token
+ await _repository.DeleteAsync(tokenEntity);
+
+ return true;
+ }
+
+ ///
+ /// Checks whether refresh token is valid asynchronously.
+ ///
+ /// ID of the user whose token is being validated.
+ /// Value of the token to be validated.
+ ///
+ /// if token is valid;
+ /// otherwise.
+ ///
+ public async Task ValidateAsync(string userId, string token)
+ {
+ var tokenEntity = await _repository.FindAsync(userId, token);
+ return tokenEntity != null && DateTime.UtcNow < tokenEntity.ValidTo;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs b/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs
new file mode 100644
index 0000000..37d44e4
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs
@@ -0,0 +1,156 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Options;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications;
+using Mapster;
+using Microsoft.Extensions.Options;
+using System.Security.Cryptography;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages invitation links.
+///
+public class InvitationLinksService(
+ IPermissionsChecker permissionsChecker,
+ IInvitationLinksRepository linksRepository,
+ ITodoListMembersRepository membersRepository,
+ IEntityExistenceChecker existenceChecker,
+ IOptions options
+ ) : IInvitationLinksService
+{
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+ private readonly IInvitationLinksRepository _linksRepository = linksRepository;
+ private readonly ITodoListMembersRepository _membersRepository = membersRepository;
+ private readonly IEntityExistenceChecker _existenceChecker = existenceChecker;
+ private readonly InvitationLinkOptions _options = options.Value;
+
+ ///
+ /// Joins the caller to the to-do list by invitation list asynchronously.
+ ///
+ /// ID of the caller.
+ /// Invitation link to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task JoinAsync(string callerId, string invitationLinkValue)
+ {
+ // Try to find a link
+ InvitationLink? invitationLink = await _linksRepository.FindAsync(invitationLinkValue);
+ if (invitationLink == null) return new(JoinByInvitationLinkStatus.NotFound);
+
+ // Check if link is still valid
+ if (invitationLink.ValidTo < DateTime.UtcNow) return new(JoinByInvitationLinkStatus.Expired);
+
+ // Check if user is not already a member
+ var member = await _membersRepository.FindAsync(invitationLink.TodoListId, callerId);
+ if (member != null) return new(JoinByInvitationLinkStatus.UserIsAlreadyMember);
+
+ // Add a new member
+ TodoListMember newMember = new()
+ {
+ TodoListId = invitationLink.TodoListId,
+ UserId = callerId
+ };
+ await _membersRepository.AddAsync(newMember);
+ var dto = newMember.Adapt();
+ return new(JoinByInvitationLinkStatus.Success, dto);
+ }
+
+ ///
+ /// Gets invitation links associated with the to-do list asynchronously.
+ ///
+ /// To-do list context of the operation.
+ /// Pagination parameters to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task>> GetInvitationLinksAsync(TodoListContext context,
+ PaginationParameters parameters)
+ {
+ // Check if to-do list exists
+ if (!await _existenceChecker.ExistsAsync(context.TodoListId))
+ return new(ServiceResponseStatus.NotFound);
+
+ // Check if user has the permission to see links
+ if (!await _permissionsChecker.HasPermissionAsync(
+ context, x => x.ManageInvitationLinks || x.AddMembers))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Get the requested page
+ InvitationLinksSpecification specification = new(context.TodoListId);
+ var page = await _linksRepository
+ .GetPageAsync(parameters, specification);
+ // Return the page
+ return new(ServiceResponseStatus.Success, page);
+ }
+
+ ///
+ /// Creates an invitation link associated to the to-do list asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task> CreateAsync(TodoListContext context)
+ {
+ // Check if to-do list exists
+ if (!await _existenceChecker.ExistsAsync(context.TodoListId))
+ return new(ServiceResponseStatus.NotFound);
+ // Check if the user has the permission
+ if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Generate the link
+ using RandomNumberGenerator rng = RandomNumberGenerator.Create();
+ byte[] valueBytes = new byte[_options.Size];
+ rng.GetBytes(valueBytes);
+
+ // Create the link
+ InvitationLink link = new()
+ {
+ TodoListId = context.TodoListId,
+ Value = Convert.ToBase64String(valueBytes),
+ ValidTo = DateTime.UtcNow.AddDays(_options.ExpirationDays)
+ };
+ // Save it
+ await _linksRepository.AddAsync(link);
+ // Map it to DTO and return
+ var result = link.Adapt();
+ return new(ServiceResponseStatus.Success, result);
+ }
+
+ ///
+ /// Deletes an invitation link associted to the to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the link.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task DeleteAsync(TodoListContext context, int linkId)
+ {
+ // Get the model of a link
+ var link = await _linksRepository.GetByIdAsync(linkId);
+ // Check if it's valid
+ if (link == null || link.TodoListId != context.TodoListId)
+ return ServiceResponseStatus.NotFound;
+ // Check if user has the permission
+ if (!await _permissionsChecker.HasPermissionAsync(context, x => x.ManageInvitationLinks))
+ return ServiceResponseStatus.Forbidden;
+
+ // Delete the link
+ await _linksRepository.DeleteAsync(link);
+
+ return ServiceResponseStatus.Success;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs
new file mode 100644
index 0000000..34bcd70
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs
@@ -0,0 +1,111 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications.Todo;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages to-do list items categories.
+///
+public class TodoItemCategoriesService(
+ ITodoListDependantEntitiesService helperService,
+ IRepository categoriesRepository
+ ) : ITodoItemCategoriesService
+{
+ private readonly ITodoListDependantEntitiesService _helperService = helperService;
+ private readonly IRepository _categoriesRepository = categoriesRepository;
+
+ ///
+ /// Asynchronously checks whether the category ID is valid for the given context.
+ ///
+ ///
+ /// ID is considered as valid.
+ ///
+ /// To-do list context.
+ /// ID of the category to validate.
+ ///
+ /// A task representing the asynchronous operation.
+ /// if ID is valid, otherwise.
+ ///
+ public async Task IsCategoryValidForContext(TodoListContext context, int? categoryId)
+ {
+ // Return true, null category is allowed for any context
+ if (categoryId == null) return true;
+
+ var category = await _categoriesRepository.GetByIdAsync(categoryId.Value);
+ return category != null && category.TodoListId == context.TodoListId;
+ }
+
+ ///
+ /// Retrieves a page of to-do list items categories of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Optional name to filter categories by.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetCategoriesOfListAsync(
+ TodoListContext context, PaginationParameters paginationParameters, string? name = null)
+ {
+ TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name);
+ return _helperService.GetPageAsync(context, specification, paginationParameters);
+ }
+
+ ///
+ /// Retrieves a to-do list item category by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context, int categoryId)
+ {
+ return _helperService.GetByIdAsync(context, categoryId);
+ }
+
+ ///
+ /// Creates a new to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto)
+ {
+ return _helperService.CreateAsync(context, dto, x => x.EditCategories);
+ }
+
+ ///
+ /// Edits a to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to edit.
+ /// The DTO containing information for editing the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto)
+ {
+ return _helperService.UpdateAsync(context, categoryId, dto, x => x.EditCategories);
+ }
+
+ ///
+ /// Deletes a to-do list item category asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+
+ public Task DeleteAsync(TodoListContext context, int categoryId)
+ {
+ return _helperService.DeleteAsync(context, categoryId, x => x.EditCategories);
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs
new file mode 100644
index 0000000..0a4098c
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs
@@ -0,0 +1,144 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications.Filters;
+using AdvancedTodoList.Core.Specifications.Todo;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages to-do lists items.
+///
+public class TodoItemsService(
+ ITodoListDependantEntitiesService helperService,
+ IRepository repository,
+ ITodoItemCategoriesService categoriesService,
+ IPermissionsChecker permissionsChecker
+ ) : ITodoItemsService
+{
+ private readonly ITodoListDependantEntitiesService _helperService = helperService;
+ private readonly IRepository _repository = repository;
+ private readonly ITodoItemCategoriesService _categoriesService = categoriesService;
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+
+ ///
+ /// Retrieves a page of to-do list items of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Filter parameters to apply.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetItemsOfListAsync(TodoListContext context,
+ PaginationParameters paginationParameters, TodoItemsFilter filter)
+ {
+ TodoItemsSpecification specification = new(context.TodoListId, filter);
+ return _helperService.GetPageAsync(context, specification, paginationParameters);
+ }
+
+ ///
+ /// Retrieves a to-do list item by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task> GetByIdAsync(TodoListContext context, int itemId)
+ {
+ // Check is user is a member of the to-do list
+ if (!await _permissionsChecker.IsMemberOfListAsync(context))
+ return new(ServiceResponseStatus.Forbidden);
+
+ TodoItemAggregateSpecification specification = new(itemId);
+ // Get the aggregate
+ var dto = await _repository.GetAggregateAsync(specification);
+ // Check if it's valid
+ if (dto == null || dto.TodoListId != context.TodoListId)
+ return new(ServiceResponseStatus.NotFound);
+
+ // Return requested DTO
+ return new(ServiceResponseStatus.Success, dto);
+ }
+
+ ///
+ /// Creates a new to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task CreateAsync(TodoListContext context, TodoItemCreateDto dto)
+ {
+ // Validate the category
+ if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId))
+ return new(TodoItemsServiceStatus.InvalidCategoryId);
+
+ var response = await _helperService.CreateAsync
+ (context, dto, x => x.AddItems);
+
+ return new(ToTodoItemsServiceStatus(response.Status), response.Result);
+ }
+
+ ///
+ /// Edits a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to edit.
+ /// The DTO containing information for editing the to-do list item.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto)
+ {
+ // Validate the category
+ if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId))
+ return TodoItemsServiceStatus.InvalidCategoryId;
+
+ var response = await _helperService.UpdateAsync(context, itemId, dto, x => x.EditItems);
+ return ToTodoItemsServiceStatus(response);
+ }
+
+ ///
+ /// Updates the state of a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to update the state.
+ /// The DTO which contains the state of the to-do item to set.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto)
+ {
+ return _helperService.UpdateAsync(context, itemId, stateDto, x => x.SetItemsState);
+ }
+
+ ///
+ /// Deletes a to-do list item asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list item to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task DeleteAsync(TodoListContext context, int itemId)
+ {
+ return _helperService.DeleteAsync(context, itemId, x => x.DeleteItems);
+ }
+
+ private static TodoItemsServiceStatus ToTodoItemsServiceStatus(ServiceResponseStatus status)
+ {
+ return status switch
+ {
+ ServiceResponseStatus.Success => TodoItemsServiceStatus.Success,
+ ServiceResponseStatus.NotFound => TodoItemsServiceStatus.NotFound,
+ ServiceResponseStatus.Forbidden => TodoItemsServiceStatus.Forbidden,
+ _ => throw new ArgumentException("Invalid service response", nameof(status))
+ };
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs
new file mode 100644
index 0000000..02251a9
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs
@@ -0,0 +1,189 @@
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications;
+using Mapster;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that performs CRUD operations
+/// on to-do list dependant entities and maps/unmaps DTOs.
+///
+/// Type of the to-do list dependant entity.
+/// Type of the unique identifier used by the entity.
+public sealed class TodoListDependantEntitiesService(
+ IRepository repository,
+ IEntityExistenceChecker existenceChecker,
+ IPermissionsChecker permissionsChecker) : ITodoListDependantEntitiesService
+ where TEntity : class, IEntity, ITodoListDependant
+ where TKey : IEquatable
+{
+ private readonly IRepository _repository = repository;
+ private readonly IEntityExistenceChecker _existenceChecker = existenceChecker;
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+
+ ///
+ /// Retrieves a page of to-do list dependant entities mapped to .
+ ///
+ ///
+ /// This method checks if to-do list ID is valid, but doesn't filter by it.
+ /// Filtering should be done in .
+ ///
+ /// To-do list context.
+ /// Specification to apply to entities.
+ /// Pagination parameters to use.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the requested page on success.
+ ///
+ public async Task>> GetPageAsync(TodoListContext context,
+ ISpecification specification, PaginationParameters paginationParameters)
+ {
+ // Check if to-do list exists
+ if (!await _existenceChecker.ExistsAsync(context.TodoListId))
+ return new(ServiceResponseStatus.NotFound);
+
+ // Check if user is a member of the to-do list
+ if (!await _permissionsChecker.IsMemberOfListAsync(context))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Get the requested page
+ var page = await _repository.GetPageAsync(paginationParameters, specification);
+ // Return the page
+ return new(ServiceResponseStatus.Success, page);
+ }
+
+ ///
+ /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to .
+ ///
+ /// To-do list context.
+ /// The ID of the entity to retrieve.
+ /// DTO to map entity to.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the requested dto on success.
+ ///
+ public async Task> GetByIdAsync(TodoListContext context, TKey entityId)
+ where TDto : class
+ {
+ // Check if user is a member of the to-do list
+ if (!await _permissionsChecker.IsMemberOfListAsync(context))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Get the model
+ var entity = await _repository.GetByIdAsync(entityId);
+ // Return null if model is null or has wrong to-do list ID
+ if (entity == null || entity.TodoListId != context.TodoListId)
+ return new(ServiceResponseStatus.NotFound);
+
+ // Map it to DTO and return
+ var result = entity.Adapt();
+ return new(ServiceResponseStatus.Success, result);
+ }
+
+ ///
+ /// Creates a new a to-do list dependant entity asynchronously from the DTO.
+ ///
+ ///
+ /// If implements the interface, then
+ /// this method will set the caller as an owner.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the entity.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation and the created mapped to
+ /// on success.
+ ///
+ public async Task> CreateAsync(
+ TodoListContext context, TInputDto dto, Func? permission = null)
+ where TOutputDto : class
+ {
+ // Check if to-do list exists
+ if (!await _existenceChecker.ExistsAsync(context.TodoListId))
+ return new(ServiceResponseStatus.NotFound);
+ // Check if the user has the permission
+ if (permission != null && !await _permissionsChecker.HasPermissionAsync(context, permission))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Create the model
+ var entity = dto.Adapt();
+ // Set the foreign key
+ entity.TodoListId = context.TodoListId;
+ // Set the owner
+ if (entity is IHasOwner hasOwnerEntity)
+ hasOwnerEntity.OwnerId = context.CallerId;
+ // Save it
+ await _repository.AddAsync(entity);
+ // Map it to DTO and return
+ var result = entity.Adapt();
+ return new(ServiceResponseStatus.Success, result);
+ }
+
+ ///
+ /// Edits a to-do list dependant entity asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the entity to edit.
+ /// The DTO containing information for editing the entity.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task UpdateAsync(
+ TodoListContext context, TKey entityId, TDto dto, Func? permission = null)
+ {
+ // Get the model of a to-do list item
+ var entity = await _repository.GetByIdAsync(entityId);
+ // Check if it's valid
+ if (entity == null || entity.TodoListId != context.TodoListId)
+ return ServiceResponseStatus.NotFound;
+
+ // Check if user has the permission
+ if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, entity, permission))
+ return ServiceResponseStatus.Forbidden;
+
+ // Update the model
+ dto.Adapt(entity);
+ // Save changes
+ await _repository.UpdateAsync(entity);
+
+ return ServiceResponseStatus.Success;
+ }
+
+ ///
+ /// Deletes a to-do list dependant entity asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the entity to delete.
+ /// Optional accessor for the permission required for the user to perform the action.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task DeleteAsync(
+ TodoListContext context, TKey entityId, Func? permission = null)
+ {
+ // Get the model of a to-do list item
+ var todoItem = await _repository.GetByIdAsync(entityId);
+ // Check if it's valid
+ if (todoItem == null || todoItem.TodoListId != context.TodoListId)
+ return ServiceResponseStatus.NotFound;
+ // Check if user has the permission
+ if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, todoItem, permission))
+ return ServiceResponseStatus.Forbidden;
+
+ // Delete the model
+ await _repository.DeleteAsync(todoItem);
+
+ return ServiceResponseStatus.Success;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs
new file mode 100644
index 0000000..dcebf99
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs
@@ -0,0 +1,146 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications;
+using AdvancedTodoList.Core.Specifications.Filters;
+using AdvancedTodoList.Core.Specifications.Todo;
+using Mapster;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages to-do list members.
+///
+public class TodoListMembersService(
+ ITodoListDependantEntitiesService helperService,
+ ITodoListMembersRepository membersRepository,
+ IRepository rolesRepository,
+ IPermissionsChecker permissionsChecker) :
+ ITodoListMembersService
+{
+ private readonly ITodoListDependantEntitiesService _helperService = helperService;
+ private readonly ITodoListMembersRepository _membersRepository = membersRepository;
+ private readonly IRepository _rolesRepository = rolesRepository;
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+
+ ///
+ /// Gets a page with members of a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Filter parameters to use.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetMembersAsync(TodoListContext context,
+ PaginationParameters paginationParameters, TodoListMembersFilter filter)
+ {
+ TodoListMembersSpecification specification = new(context.TodoListId, filter);
+ return _helperService.GetPageAsync(
+ context, specification, paginationParameters
+ );
+ }
+
+ ///
+ /// Adds a member to a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// DTO that contains information needed for adding a member. Supossed to be valid.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto)
+ {
+ // Check if user has the permission
+ if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers))
+ return new(TodoListMemberServiceResultStatus.Forbidden);
+
+ // Try to find already existing member
+ var member = await _membersRepository.FindAsync(context.TodoListId, dto.UserId);
+ // Return error if it exists
+ if (member != null) return new(TodoListMemberServiceResultStatus.UserAlreadyAdded);
+
+ // Add member
+ var response = await _helperService
+ .CreateAsync(context, dto);
+
+ return response.Status switch
+ {
+ ServiceResponseStatus.Success => new(TodoListMemberServiceResultStatus.Success, response.Result),
+ ServiceResponseStatus.NotFound => new(TodoListMemberServiceResultStatus.NotFound),
+ _ => throw new InvalidOperationException("Invalid to-do lists dependant entities (members) service response.")
+ };
+ }
+
+ ///
+ /// Updates a role of the member of a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the member.
+ /// DTO that contains information needed for updating a role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task UpdateMemberRoleAsync(TodoListContext context, int memberId, TodoListMemberUpdateRoleDto dto)
+ {
+ // I think that this method does too much and has many test cases, probably needs refactoring
+
+ // Check if the caller has the permission to assign roles
+ MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId);
+ var caller = await _membersRepository.GetAggregateAsync(specification);
+ if (caller == null || caller.Role == null || !caller.Role.Permissions.AssignRoles)
+ return TodoListMemberServiceResultStatus.Forbidden;
+
+ // Check if user has the permission to assign the role
+ if (dto.RoleId != null)
+ {
+ var role = await _rolesRepository.GetByIdAsync(dto.RoleId.Value);
+ // Validate RoleId
+ if (role == null || role.TodoListId != context.TodoListId)
+ return TodoListMemberServiceResultStatus.InvalidRoleId;
+ // Check priority
+ if (role.Priority <= caller.Role.Priority)
+ return TodoListMemberServiceResultStatus.Forbidden;
+ }
+
+ // Get the model of a member
+ var member = await _membersRepository.GetByIdAsync(memberId);
+ // Check if it's valid
+ if (member == null || member.TodoListId != context.TodoListId)
+ return TodoListMemberServiceResultStatus.NotFound;
+
+ // Check if user has a permission to assign roles to the member
+ if (member.RoleId != null)
+ {
+ // Get the member's role
+ var role = await _rolesRepository.GetByIdAsync(member.RoleId.Value) ??
+ throw new InvalidOperationException("Member's role is not found");
+
+ if (role.Priority <= caller.Role.Priority)
+ return TodoListMemberServiceResultStatus.Forbidden;
+ }
+
+ // Update the model
+ dto.Adapt(member);
+ // Save changes
+ await _membersRepository.UpdateAsync(member);
+
+ return TodoListMemberServiceResultStatus.Success;
+ }
+
+ ///
+ /// Removes a member from a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// ID of the member.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task RemoveMemberAsync(TodoListContext context, int memberId)
+ {
+ return _helperService.DeleteAsync(context, memberId, x => x.RemoveMembers);
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs
new file mode 100644
index 0000000..7ce4aed
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs
@@ -0,0 +1,132 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications.Todo;
+using Mapster;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages to-do lists roles.
+///
+public class TodoListRolesService(
+ ITodoListDependantEntitiesService helperService,
+ IRepository rolesRepository,
+ IPermissionsChecker permissionsChecker
+ ) : ITodoListRolesService
+{
+ private readonly ITodoListDependantEntitiesService _helperService = helperService;
+ private readonly IRepository _rolesRepository = rolesRepository;
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+
+ ///
+ /// Retrieves a page of to-do list roles of the list with the specified ID.
+ ///
+ /// To-do list context.
+ /// Pagination parameters to use.
+ /// Optional name to filter categories by.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task>> GetRolesOfListAsync(
+ TodoListContext context, PaginationParameters paginationParameters, string? name = null)
+ {
+ TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name);
+ return _helperService.GetPageAsync(context, specification, paginationParameters);
+ }
+
+ ///
+ /// Retrieves a to-do list role by its ID asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to retrieve.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetByIdAsync(TodoListContext context, int roleId)
+ {
+ return _helperService.GetByIdAsync(context, roleId);
+ }
+
+ ///
+ /// Creates a new to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for creating the to-do list role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ /// It's not practically possible for this implementation to have the result that indicates
+ /// 'NotFound' status.
+ ///
+ public async Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto)
+ {
+ // Check if user has a permission to create a role with the given priority
+ if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, dto.Priority, x => x.EditRoles))
+ return new(ServiceResponseStatus.Forbidden);
+
+ // Pass null as the third argument to not check permissions twice
+ return await _helperService.CreateAsync(
+ context, dto, null);
+ }
+
+ ///
+ /// Edits a to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to edit.
+ /// The DTO containing information for editing the to-do list role.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto)
+ {
+ // Get the model of a role
+ var role = await _rolesRepository.GetByIdAsync(roleId);
+ // Check if it's valid
+ if (role == null || role.TodoListId != context.TodoListId)
+ return ServiceResponseStatus.NotFound;
+
+ // Check if user has a permission to change it
+ // Checking only the minimal priority is sufficient, because both
+ // values should be greater than caller's role priority.
+ int minPriority = Math.Min(role.Priority, dto.Priority);
+ if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, minPriority, x => x.EditRoles))
+ return ServiceResponseStatus.Forbidden;
+
+ // Update the model
+ dto.Adapt(role);
+ // Save changes
+ await _rolesRepository.UpdateAsync(role);
+
+ return ServiceResponseStatus.Success;
+ }
+
+ ///
+ /// Deletes a to-do list role asynchronously.
+ ///
+ /// To-do list context.
+ /// The ID of the to-do list role to delete.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public async Task DeleteAsync(TodoListContext context, int roleId)
+ {
+ // Get the model of a role
+ var role = await _rolesRepository.GetByIdAsync(roleId);
+ // Check if it's valid
+ if (role == null || role.TodoListId != context.TodoListId)
+ return ServiceResponseStatus.NotFound;
+
+ // Check if user has the permission to delete the role
+ if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, role.Priority, x => x.EditRoles))
+ return ServiceResponseStatus.Forbidden;
+
+ // Delete the role
+ await _rolesRepository.DeleteAsync(role);
+
+ return ServiceResponseStatus.Success;
+ }
+}
diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs
new file mode 100644
index 0000000..2fd8a53
--- /dev/null
+++ b/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs
@@ -0,0 +1,182 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Application.Services.Definitions;
+using AdvancedTodoList.Application.Services.Definitions.Auth;
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Pagination;
+using AdvancedTodoList.Core.Repositories;
+using AdvancedTodoList.Core.Specifications.Filters;
+using AdvancedTodoList.Core.Specifications.Todo;
+using Mapster;
+
+namespace AdvancedTodoList.Application.Services.Implementations;
+
+///
+/// A service that manages to-do lists.
+///
+public class TodoListsService(
+ IPermissionsChecker permissionsChecker,
+ IRepository todoListsRepository,
+ IRepository rolesRepository,
+ ITodoListMembersRepository membersRepository,
+ IUnitOfWork unitOfWork
+ ) : ITodoListsService
+{
+ private readonly IPermissionsChecker _permissionsChecker = permissionsChecker;
+ private readonly IRepository _todoListsRepository = todoListsRepository;
+ private readonly IRepository _rolesRepository = rolesRepository;
+ private readonly IRepository _membersRepository = membersRepository;
+ private readonly IUnitOfWork _unitOfWork = unitOfWork;
+
+ ///
+ /// Retrieves a page of to-do lists, with the requirement that the user
+ /// is a member of those lists.
+ ///
+ /// Id of the user
+ /// Pagination parameters to use.
+ /// Filter parameters to apply.
+ ///
+ /// A task representing the asynchronous operation containing the result of operation.
+ ///
+ public Task> GetListsOfUserAsync(string userId,
+ PaginationParameters paginationParameters, TodoListsFilter filter)
+ {
+ TodoListsSpecification specification = new(userId, filter);
+ return _todoListsRepository.GetPageAsync(
+ paginationParameters, specification);
+ }
+
+ ///
+ /// Retrieves a to-do list by its ID asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task> GetByIdAsync(TodoListContext context)
+ {
+ TodoListAggregateSpecification specification = new(context.TodoListId);
+ var result = await _todoListsRepository.GetAggregateAsync(specification);
+ // Check if to-do list exists
+ if (result == null) return new(ServiceResponseStatus.NotFound);
+
+ // Check if the user has a permission to view the to-do list
+ if (!await _permissionsChecker.IsMemberOfListAsync(context))
+ return new(ServiceResponseStatus.Forbidden);
+
+ return new(ServiceResponseStatus.Success, result);
+ }
+
+ ///
+ /// Creates a new to-do list asynchronously.
+ ///
+ ///
+ /// This method should also create an "Owner" role with all permissions and assign the caller to it.
+ ///
+ /// The DTO containing information for creating the to-do list.
+ /// ID of the user who creates the to-do list.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a created model mapped to .
+ ///
+ public async Task CreateAsync(TodoListCreateDto dto, string callerId)
+ {
+ // Map DTO to the model
+ var todoList = dto.Adapt();
+ // Set the owner
+ todoList.OwnerId = callerId;
+
+ // Begin a transaction
+ await _unitOfWork.BeginTransactionAsync();
+
+ try
+ {
+ // Add the list to the database
+ await _todoListsRepository.AddAsync(todoList);
+
+ // Create an "Owner" role
+ TodoListRole ownerRole = new()
+ {
+ Name = "Owner",
+ Priority = 0,
+ TodoListId = todoList.Id,
+ Permissions = RolePermissions.All
+ };
+ await _rolesRepository.AddAsync(ownerRole);
+
+ // Assign the caller to it
+ TodoListMember member = new()
+ {
+ UserId = callerId,
+ TodoListId = todoList.Id,
+ RoleId = ownerRole.Id
+ };
+ await _membersRepository.AddAsync(member);
+ }
+ catch (Exception)
+ {
+ // Rollback in a case of error
+ await _unitOfWork.RollbackAsync();
+ throw;
+ }
+
+ // Commit changes
+ await _unitOfWork.CommitAsync();
+
+ // Return a DTO of created model
+ return todoList.Adapt();
+ }
+
+ ///
+ /// Edits a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ /// The DTO containing information for editing the to-do list.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task EditAsync(TodoListContext context, TodoListCreateDto dto)
+ {
+ // Get the model
+ var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId);
+ // Return NotFound if the model doesn't exist
+ if (todoList == null) return ServiceResponseStatus.NotFound;
+ // Check if the user has a permission to edit the list
+ if (!await _permissionsChecker.CanTouchEntityAsync(
+ context, todoList, x => x.EditItems))
+ {
+ return ServiceResponseStatus.Forbidden;
+ }
+
+ // Update the model
+ dto.Adapt(todoList);
+ await _todoListsRepository.UpdateAsync(todoList);
+
+ return ServiceResponseStatus.Success;
+ }
+
+ ///
+ /// Deletes a to-do list asynchronously.
+ ///
+ /// To-do list context.
+ ///
+ /// A task representing the asynchronous operation. The task contains
+ /// a result of the operation.
+ ///
+ public async Task DeleteAsync(TodoListContext context)
+ {
+ // Get the model
+ var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId);
+ // Return NotFound if the model doesn't exist
+ if (todoList == null) return ServiceResponseStatus.NotFound;
+ // Check if the user is an owner of the list
+ if (todoList.OwnerId != context.CallerId) return ServiceResponseStatus.Forbidden;
+
+ // Delete the model
+ await _todoListsRepository.DeleteAsync(todoList);
+
+ return ServiceResponseStatus.Success;
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs
new file mode 100644
index 0000000..f12a23b
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs
@@ -0,0 +1,17 @@
+using AdvancedTodoList.Application.Dtos;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation.Auth;
+
+public class LogInDtoValidator : AbstractValidator
+{
+ public LogInDtoValidator()
+ {
+ RuleFor(x => x.UserNameOrEmail)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ RuleFor(x => x.Password)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs
new file mode 100644
index 0000000..51da4f1
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs
@@ -0,0 +1,14 @@
+using AdvancedTodoList.Application.Dtos;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation.Auth;
+
+public class LogOutDtoValidator : AbstractValidator
+{
+ public LogOutDtoValidator()
+ {
+ RuleFor(x => x.RefreshToken)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs
new file mode 100644
index 0000000..b125aff
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs
@@ -0,0 +1,18 @@
+using AdvancedTodoList.Application.Dtos;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation.Auth;
+
+public class RefreshDtoValidator : AbstractValidator
+{
+ public RefreshDtoValidator()
+ {
+ RuleFor(x => x.AccessToken)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ RuleFor(x => x.RefreshToken)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs
new file mode 100644
index 0000000..23f8c38
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs
@@ -0,0 +1,34 @@
+using AdvancedTodoList.Application.Dtos;
+using FluentValidation;
+using FluentValidation.Validators;
+
+namespace AdvancedTodoList.Application.Validation.Auth;
+
+public class RegisterDtoValidator : AbstractValidator
+{
+ public RegisterDtoValidator()
+ {
+ RuleFor(x => x.Email)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ RuleFor(x => x.Email)
+ .EmailAddress(EmailValidationMode.AspNetCoreCompatible)
+ .WithErrorCode(ValidationErrorCodes.InvalidEmail);
+
+ RuleFor(x => x.UserName)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ RuleFor(x => x.FirstName)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ RuleFor(x => x.LastName)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ RuleFor(x => x.Password)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs b/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs
new file mode 100644
index 0000000..ad59aa8
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs
@@ -0,0 +1,25 @@
+using AdvancedTodoList.Core.Pagination;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+public class PaginationParametersValidator : AbstractValidator
+{
+ public PaginationParametersValidator()
+ {
+ RuleFor(x => x.Page)
+ .GreaterThan(0)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage("Page number must be a positive number.");
+
+ RuleFor(x => x.PageSize)
+ .GreaterThanOrEqualTo(PaginationParameters.MinPageSize)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}");
+
+ RuleFor(x => x.PageSize)
+ .LessThanOrEqualTo(PaginationParameters.MaxPageSize)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}");
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs
new file mode 100644
index 0000000..a41394d
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs
@@ -0,0 +1,21 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+public class TodoItemCategoryCreateDtoValidator : AbstractValidator
+{
+ public TodoItemCategoryCreateDtoValidator()
+ {
+ // Name is required
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ // Name should not be too long
+ RuleFor(x => x.Name)
+ .MaximumLength(TodoItemCategory.NameMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs
new file mode 100644
index 0000000..83be7d7
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs
@@ -0,0 +1,49 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+///
+/// Validator class for
+///
+public class TodoItemCreateDtoValidator : AbstractValidator
+{
+ public TodoItemCreateDtoValidator()
+ {
+ // Name is required
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ // Name should not be too long
+ RuleFor(x => x.Name)
+ .MaximumLength(TodoItem.NameMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+
+ // Description is not null
+ RuleFor(x => x.Description)
+ .NotNull()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ // Description should not be too long
+ RuleFor(x => x.Description)
+ .MaximumLength(TodoItem.DescriptionMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+
+ // Deadline date should be after the current date
+ RuleFor(x => x.DeadlineDate)
+ .GreaterThan(DateTime.UtcNow)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange);
+
+ // Priority must be in a specific range
+ RuleFor(x => x.Priority)
+ .GreaterThanOrEqualTo(TodoItem.MinPriority)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}");
+ RuleFor(x => x.Priority)
+ .LessThanOrEqualTo(TodoItem.MaxPriority)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}");
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs
new file mode 100644
index 0000000..f03d82c
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs
@@ -0,0 +1,16 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+public class TodoItemUpdateStateDtoValidator : AbstractValidator
+{
+ public TodoItemUpdateStateDtoValidator()
+ {
+ RuleFor(x => x.State)
+ .Must(s => s >= TodoItemState.Active && s <= TodoItemState.Skipped)
+ .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
+ .WithMessage(x => $"{(int)x.State} is invalid value for {{PropertyName}}");
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs
new file mode 100644
index 0000000..9aeeccf
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs
@@ -0,0 +1,31 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+public class TodoListCreateDtoValidator : AbstractValidator
+{
+ public TodoListCreateDtoValidator()
+ {
+ // Name is required
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ // Name should not be too long
+ RuleFor(x => x.Name)
+ .MaximumLength(TodoList.NameMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+
+ // Description is not null
+ RuleFor(x => x.Description)
+ .NotNull()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ // Description should not be too long
+ RuleFor(x => x.Description)
+ .MaximumLength(TodoList.DescriptionMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs
new file mode 100644
index 0000000..59be32e
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs
@@ -0,0 +1,27 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.Auth;
+using AdvancedTodoList.Core.Repositories;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+///
+/// Validator class for
+///
+public class TodoListMemberAddDtoValidator : AbstractValidator
+{
+ public TodoListMemberAddDtoValidator(IEntityExistenceChecker existenceChecker)
+ {
+ RuleFor(x => x.UserId)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+
+ RuleFor(x => x.UserId)
+ .MustAsync(async (userId, _) =>
+ {
+ return await existenceChecker.ExistsAsync(userId);
+ })
+ .WithErrorCode(ValidationErrorCodes.InvalidForeignKey)
+ .WithMessage("User not found.");
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs
new file mode 100644
index 0000000..bc8f284
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs
@@ -0,0 +1,25 @@
+using AdvancedTodoList.Application.Dtos;
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using FluentValidation;
+
+namespace AdvancedTodoList.Application.Validation;
+
+///
+/// Validator class for
+///
+public class TodoListRoleCreateDtoValidator : AbstractValidator
+{
+ public TodoListRoleCreateDtoValidator()
+ {
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ RuleFor(x => x.Name)
+ .MaximumLength(TodoListRole.NameMaxLength)
+ .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
+
+ RuleFor(x => x.Permissions)
+ .NotNull()
+ .WithErrorCode(ValidationErrorCodes.PropertyRequired);
+ }
+}
diff --git a/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs b/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs
new file mode 100644
index 0000000..d2ba001
--- /dev/null
+++ b/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs
@@ -0,0 +1,28 @@
+namespace AdvancedTodoList.Application.Validation;
+
+///
+/// Class that contains validation error codes.
+///
+public static class ValidationErrorCodes
+{
+ ///
+ /// Required property is null or empty.
+ ///
+ public const string PropertyRequired = "100";
+ ///
+ /// Length of the property exceeds maximum possible value.
+ ///
+ public const string PropertyTooLong = "200";
+ ///
+ /// Value of the property is out of range.
+ ///
+ public const string PropertyOutOfRange = "300";
+ ///
+ /// Property is invalid foreign key.
+ ///
+ public const string InvalidForeignKey = "400";
+ ///
+ /// Property is invalid email.
+ ///
+ public const string InvalidEmail = "500";
+}
diff --git a/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj b/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj
index 399f334..51845b4 100644
--- a/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj
+++ b/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj
@@ -7,9 +7,9 @@
-
+
-
+
diff --git a/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs b/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs
deleted file mode 100644
index b163ae8..0000000
--- a/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using AdvancedTodoList.Core.Models.TodoLists;
-
-namespace AdvancedTodoList.Core.Dtos;
-
-///
-/// DTO for creating/editing a to-do list item.
-///
-public record TodoItemCreateDto(
- string Name, string Description, DateTime? DeadlineDate,
- int Priority, int? CategoryId
- );
-
-///
-/// DTO for changing the state of a to-do list item.
-///
-public record TodoItemUpdateStateDto(TodoItemState State);
-
-///
-/// DTO for a full view of a to-do list item.
-///
-public record TodoItemGetByIdDto(
- int Id, string TodoListId, string Name,
- string Description, DateTime? DeadlineDate,
- TodoItemState State, int Priority,
- ApplicationUserPreviewDto Owner,
- TodoItemCategoryViewDto? Category
- );
-
-///
-/// DTO for a partial view of a to-do list item.
-///
-public record TodoItemPreviewDto(
- int Id, string TodoListId, string Name,
- DateTime? DeadlineDate, TodoItemState State,
- int Priority, ApplicationUserPreviewDto Owner,
- TodoItemCategoryViewDto? Category
- );
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs b/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs
deleted file mode 100644
index f6cea61..0000000
--- a/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists;
-using AdvancedTodoList.Core.Models.TodoLists.Members;
-using Mapster;
-
-namespace AdvancedTodoList.Core.Mapping;
-
-///
-/// Class that defines global mapping settings.
-///
-public static class MappingGlobalSettings
-{
- ///
- /// Apply global mapping settings.
- ///
- public static void Apply()
- {
- // Ignore null IDs
- TypeAdapterConfig.NewConfig()
- .IgnoreIf((src, dest) => src.RoleId == null, dest => dest.Role!);
- TypeAdapterConfig.NewConfig()
- .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!);
- TypeAdapterConfig.NewConfig()
- .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!);
-
- // Convert null strings into empty strings and trim strings
- TypeAdapterConfig.GlobalSettings.Default
- .AddDestinationTransform((string? dest) => dest != null ? dest.Trim() : string.Empty);
- }
-}
diff --git a/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs b/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs
index 11660e1..cc05b7c 100644
--- a/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs
+++ b/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs
@@ -5,13 +5,13 @@ namespace AdvancedTodoList.Core.Models.Auth;
public class ApplicationUser : IdentityUser, IEntity
{
- [MaxLength(MaxNameLength)]
- public required string FirstName { get; set; }
- [MaxLength(MaxNameLength)]
- public required string LastName { get; set; }
+ [MaxLength(MaxNameLength)]
+ public required string FirstName { get; set; }
+ [MaxLength(MaxNameLength)]
+ public required string LastName { get; set; }
- ///
- /// Maximum length for properties and .
- ///
- public const int MaxNameLength = 100;
+ ///
+ /// Maximum length for properties and .
+ ///
+ public const int MaxNameLength = 100;
}
diff --git a/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs b/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs
index 8e9f067..df022f4 100644
--- a/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs
+++ b/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs
@@ -8,29 +8,29 @@ namespace AdvancedTodoList.Core.Models.Auth;
///
public class UserRefreshToken : IEntity
{
- ///
- /// The unique identifier.
- ///
- [Key]
- public int Id { get; set; }
+ ///
+ /// The unique identifier.
+ ///
+ [Key]
+ public int Id { get; set; }
- ///
- /// A foreign key to the user who owns the token.
- ///
- [ForeignKey(nameof(User))]
- public required string UserId { get; set; }
+ ///
+ /// A foreign key to the user who owns the token.
+ ///
+ [ForeignKey(nameof(User))]
+ public required string UserId { get; set; }
- ///
- /// A value of the token.
- ///
- public required string Token { get; set; }
- ///
- /// Date after which token becomes invalid.
- ///
- public required DateTime ValidTo { get; set; }
+ ///
+ /// A value of the token.
+ ///
+ public required string Token { get; set; }
+ ///
+ /// Date after which token becomes invalid.
+ ///
+ public required DateTime ValidTo { get; set; }
- ///
- /// A navigation property for a user which owns the token.
- ///
- public ApplicationUser User { get; set; } = null!;
+ ///
+ /// A navigation property for a user which owns the token.
+ ///
+ public ApplicationUser User { get; set; } = null!;
}
diff --git a/AdvancedTodoList.Core/Models/IEntity.cs b/AdvancedTodoList.Core/Models/IEntity.cs
index 7a31f73..0ab02d6 100644
--- a/AdvancedTodoList.Core/Models/IEntity.cs
+++ b/AdvancedTodoList.Core/Models/IEntity.cs
@@ -6,5 +6,5 @@
/// Type of the entity ID.
public interface IEntity where TId : IEquatable
{
- TId Id { get; }
+ TId Id { get; }
}
diff --git a/AdvancedTodoList.Core/Models/IHasName.cs b/AdvancedTodoList.Core/Models/IHasName.cs
index 981c8b3..643d5f7 100644
--- a/AdvancedTodoList.Core/Models/IHasName.cs
+++ b/AdvancedTodoList.Core/Models/IHasName.cs
@@ -6,5 +6,5 @@
///
public interface IHasName
{
- public string Name { get; }
+ public string Name { get; }
}
diff --git a/AdvancedTodoList.Core/Models/IHasOwner.cs b/AdvancedTodoList.Core/Models/IHasOwner.cs
index 1e03816..a0a5f0b 100644
--- a/AdvancedTodoList.Core/Models/IHasOwner.cs
+++ b/AdvancedTodoList.Core/Models/IHasOwner.cs
@@ -5,8 +5,8 @@
///
public interface IHasOwner
{
- ///
- /// Foreign key referencing the user who created this entity.
- ///
- public string? OwnerId { get; set; }
+ ///
+ /// Foreign key referencing the user who created this entity.
+ ///
+ public string? OwnerId { get; set; }
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs b/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs
index f9dd8db..79b4fff 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs
@@ -5,8 +5,8 @@
///
public interface ITodoListDependant
{
- ///
- /// A foreign key of a to-do list.
- ///
- string TodoListId { get; set; }
+ ///
+ /// A foreign key of a to-do list.
+ ///
+ string TodoListId { get; set; }
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs b/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs
index b88dc02..bf6b909 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs
@@ -8,29 +8,29 @@ namespace AdvancedTodoList.Core.Models.TodoLists;
///
public class InvitationLink : IEntity
{
- ///
- /// A unique identifier for the to-do list item.
- ///
- [Key]
- public int Id { get; set; }
+ ///
+ /// A unique identifier for the to-do list item.
+ ///
+ [Key]
+ public int Id { get; set; }
- ///
- /// Foreign key of the to-do list where the link is active.
- ///
- [ForeignKey(nameof(TodoList))]
- public required string TodoListId { get; set; }
- ///
- /// Navigation property to the to-do list associated with this link.
- ///
- public TodoList TodoList { get; set; } = null!;
+ ///
+ /// Foreign key of the to-do list where the link is active.
+ ///
+ [ForeignKey(nameof(TodoList))]
+ public required string TodoListId { get; set; }
+ ///
+ /// Navigation property to the to-do list associated with this link.
+ ///
+ public TodoList TodoList { get; set; } = null!;
- ///
- /// A unique string value representing the link.
- ///
- public required string Value { get; set; }
+ ///
+ /// A unique string value representing the link.
+ ///
+ public required string Value { get; set; }
- ///
- /// Date after which the link becomes invalid.
- ///
- public DateTime ValidTo { get; set; }
+ ///
+ /// Date after which the link becomes invalid.
+ ///
+ public DateTime ValidTo { get; set; }
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs
index 100065e..59bfba2 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs
@@ -14,20 +14,20 @@
/// A flag that determines whether user can edit/delete existing categories and add new categories.
/// A flag that determines whether user can view/delete existing invitation links.
public record struct RolePermissions(
- bool SetItemsState = false,
- bool AddItems = false,
- bool EditItems = false,
- bool DeleteItems = false,
- bool AddMembers = false,
- bool RemoveMembers = false,
- bool AssignRoles = false,
- bool EditRoles = false,
- bool EditCategories = false,
- bool ManageInvitationLinks = false
- )
+ bool SetItemsState = false,
+ bool AddItems = false,
+ bool EditItems = false,
+ bool DeleteItems = false,
+ bool AddMembers = false,
+ bool RemoveMembers = false,
+ bool AssignRoles = false,
+ bool EditRoles = false,
+ bool EditCategories = false,
+ bool ManageInvitationLinks = false
+ )
{
- ///
- /// Instance of a structure with all permissions.
- ///
- public static readonly RolePermissions All = new(true, true, true, true, true, true, true, true, true, true);
+ ///
+ /// Instance of a structure with all permissions.
+ ///
+ public static readonly RolePermissions All = new(true, true, true, true, true, true, true, true, true, true);
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs
index ff84356..686bf37 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs
@@ -9,39 +9,39 @@ namespace AdvancedTodoList.Core.Models.TodoLists.Members;
///
public class TodoListMember : IEntity, ITodoListDependant
{
- ///
- /// An unique identifier.
- ///
- [Key]
- public int Id { get; set; }
+ ///
+ /// An unique identifier.
+ ///
+ [Key]
+ public int Id { get; set; }
- ///
- /// A foreign key of the user who is the member.
- ///
- [ForeignKey(nameof(User))]
- public required string UserId { get; set; }
- ///
- /// A foreign key of the to-do list.
- ///
- [ForeignKey(nameof(TodoList))]
- public required string TodoListId { get; set; }
- ///
- /// A foreign key of the role, if null user has no role
- /// and has a read-only access.
- ///
- [ForeignKey(nameof(Role))]
- public int? RoleId { get; set; }
+ ///
+ /// A foreign key of the user who is the member.
+ ///
+ [ForeignKey(nameof(User))]
+ public required string UserId { get; set; }
+ ///
+ /// A foreign key of the to-do list.
+ ///
+ [ForeignKey(nameof(TodoList))]
+ public required string TodoListId { get; set; }
+ ///
+ /// A foreign key of the role, if null user has no role
+ /// and has a read-only access.
+ ///
+ [ForeignKey(nameof(Role))]
+ public int? RoleId { get; set; }
- ///
- /// A navigation property to the user who is the member.
- ///
- public ApplicationUser User { get; set; } = null!;
- ///
- /// A navigation property to the to-do list.
- ///
- public TodoList TodoList { get; set; } = null!;
- ///
- /// A navigation property to the role.
- ///
- public TodoListRole? Role { get; set; }
+ ///
+ /// A navigation property to the user who is the member.
+ ///
+ public ApplicationUser User { get; set; } = null!;
+ ///
+ /// A navigation property to the to-do list.
+ ///
+ public TodoList TodoList { get; set; } = null!;
+ ///
+ /// A navigation property to the role.
+ ///
+ public TodoListRole? Role { get; set; }
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs
index fd16697..faa9185 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs
@@ -8,42 +8,42 @@ namespace AdvancedTodoList.Core.Models.TodoLists.Members;
///
public class TodoListRole : IEntity, ITodoListDependant, IHasName
{
- ///
- /// A unique identifier.
- ///
- [Key]
- public int Id { get; set; }
- ///
- /// Name of the role.
- ///
- [MaxLength(NameMaxLength)]
- public required string Name { get; set; }
- ///
- /// Priority of the role.
- ///
- ///
- /// The lower value, the higher role is. 0 is reserved for the 'Owner' role which is created
- /// together with a to-do list. Roles with negative priorities should not be possible.
- ///
- public required int Priority { get; set; }
+ ///
+ /// A unique identifier.
+ ///
+ [Key]
+ public int Id { get; set; }
+ ///
+ /// Name of the role.
+ ///
+ [MaxLength(NameMaxLength)]
+ public required string Name { get; set; }
+ ///
+ /// Priority of the role.
+ ///
+ ///
+ /// The lower value, the higher role is. 0 is reserved for the 'Owner' role which is created
+ /// together with a to-do list. Roles with negative priorities should not be possible.
+ ///
+ public required int Priority { get; set; }
- ///
- /// A foreign key of the to-do list which has this role.
- ///
- [ForeignKey(nameof(TodoList))]
- public required string TodoListId { get; set; }
- ///
- /// A navigation property to the to-do list which has this role.
- ///
- public TodoList TodoList { get; set; } = null!;
+ ///
+ /// A foreign key of the to-do list which has this role.
+ ///
+ [ForeignKey(nameof(TodoList))]
+ public required string TodoListId { get; set; }
+ ///
+ /// A navigation property to the to-do list which has this role.
+ ///
+ public TodoList TodoList { get; set; } = null!;
- ///
- /// Permissions which each member with this role has.
- ///
- public RolePermissions Permissions { get; set; } = new();
+ ///
+ /// Permissions which each member with this role has.
+ ///
+ public RolePermissions Permissions { get; set; } = new();
- ///
- /// Max length of the property.
- ///
- public const int NameMaxLength = 100;
+ ///
+ /// Max length of the property.
+ ///
+ public const int NameMaxLength = 100;
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs
index 461cbaf..1c027b7 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs
@@ -9,81 +9,81 @@ namespace AdvancedTodoList.Core.Models.TodoLists;
///
public class TodoItem : IEntity, ITodoListDependant, IHasOwner
{
- ///
- /// An unique identifier for the to-do list item.
- ///
- [Key]
- public int Id { get; set; }
- ///
- /// Name (title) of the to-do item.
- ///
- [MaxLength(NameMaxLength)]
- public required string Name { get; set; }
- ///
- /// Description of the to-do item.
- ///
- [MaxLength(DescriptionMaxLength)]
- public required string Description { get; set; }
- ///
- /// Current state of the to-do item.
- ///
- public TodoItemState State { get; set; }
- ///
- /// Deadline date for the todo item. Can be null.
- ///
- public DateTime? DeadlineDate { get; set; }
- ///
- /// Priority of the item. 0 means the lowest, 10 means the highest.
- ///
- public int Priority { get; set; } = 0;
+ ///
+ /// An unique identifier for the to-do list item.
+ ///
+ [Key]
+ public int Id { get; set; }
+ ///
+ /// Name (title) of the to-do item.
+ ///
+ [MaxLength(NameMaxLength)]
+ public required string Name { get; set; }
+ ///
+ /// Description of the to-do item.
+ ///
+ [MaxLength(DescriptionMaxLength)]
+ public required string Description { get; set; }
+ ///
+ /// Current state of the to-do item.
+ ///
+ public TodoItemState State { get; set; }
+ ///
+ /// Deadline date for the todo item. Can be null.
+ ///
+ public DateTime? DeadlineDate { get; set; }
+ ///
+ /// Priority of the item. 0 means the lowest, 10 means the highest.
+ ///
+ public int Priority { get; set; } = 0;
- ///
- /// Foreign key referencing the associated category.
- ///
- [ForeignKey(nameof(Category))]
- public int? CategoryId { get; set; }
- ///
- /// Navigation property to the category associated with this to-do item.
- ///
- public TodoItemCategory? Category { get; set; }
+ ///
+ /// Foreign key referencing the associated category.
+ ///
+ [ForeignKey(nameof(Category))]
+ public int? CategoryId { get; set; }
+ ///
+ /// Navigation property to the category associated with this to-do item.
+ ///
+ public TodoItemCategory? Category { get; set; }
- ///
- /// Foreign key referencing the associated to-do list.
- ///
- [ForeignKey(nameof(TodoList))]
- public required string TodoListId { get; set; }
- ///
- /// Navigation property to the to-do list associated with this to-do item.
- ///
- public TodoList TodoList { get; set; } = null!;
+ ///
+ /// Foreign key referencing the associated to-do list.
+ ///
+ [ForeignKey(nameof(TodoList))]
+ public required string TodoListId { get; set; }
+ ///
+ /// Navigation property to the to-do list associated with this to-do item.
+ ///
+ public TodoList TodoList { get; set; } = null!;
- ///
- /// Foreign key referencing the user who created this item.
- ///
- [ForeignKey(nameof(Owner))]
- public required string? OwnerId { get; set; } = null!;
- ///
- /// Navigation property to the user who created this item.
- ///
- public ApplicationUser? Owner { get; set; }
+ ///
+ /// Foreign key referencing the user who created this item.
+ ///
+ [ForeignKey(nameof(Owner))]
+ public required string? OwnerId { get; set; } = null!;
+ ///
+ /// Navigation property to the user who created this item.
+ ///
+ public ApplicationUser? Owner { get; set; }
- ///
- /// Maximum allowed length of .
- ///
- public const int NameMaxLength = 100;
- ///
- /// Maximum allowed length of .
- ///
- public const int DescriptionMaxLength = 10_000;
+ ///
+ /// Maximum allowed length of .
+ ///
+ public const int NameMaxLength = 100;
+ ///
+ /// Maximum allowed length of .
+ ///
+ public const int DescriptionMaxLength = 10_000;
- ///
- /// Minimum allowed value of
- ///
- public const int MinPriority = 0;
- ///
- /// Maximum allowed value of
- ///
- public const int MaxPriority = 10;
+ ///
+ /// Minimum allowed value of
+ ///
+ public const int MinPriority = 0;
+ ///
+ /// Maximum allowed value of
+ ///
+ public const int MaxPriority = 10;
}
///
@@ -91,16 +91,16 @@ public class TodoItem : IEntity, ITodoListDependant, IHasOwner
///
public enum TodoItemState : byte
{
- ///
- /// The task is active (default state).
- ///
- Active = 0,
- ///
- /// The task has been completed.
- ///
- Completed,
- ///
- /// The task has been skipped.
- ///
- Skipped
+ ///
+ /// The task is active (default state).
+ ///
+ Active = 0,
+ ///
+ /// The task has been completed.
+ ///
+ Completed,
+ ///
+ /// The task has been skipped.
+ ///
+ Skipped
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs
index 0a10d0b..67ae24b 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs
@@ -8,34 +8,34 @@ namespace AdvancedTodoList.Core.Models.TodoLists;
///
public class TodoItemCategory : IEntity, ITodoListDependant, IHasName
{
- ///
- /// An unique identifier for the category.
- ///
- [Key]
- public int Id { get; set; }
- ///
- /// Name of the category.
- ///
- [MaxLength(NameMaxLength)]
- public required string Name { get; set; }
+ ///
+ /// An unique identifier for the category.
+ ///
+ [Key]
+ public int Id { get; set; }
+ ///
+ /// Name of the category.
+ ///
+ [MaxLength(NameMaxLength)]
+ public required string Name { get; set; }
- ///
- /// Foreign key referencing the associated to-do list.
- ///
- [ForeignKey(nameof(TodoList))]
- public required string TodoListId { get; set; }
- ///
- /// Navigation property to the to-do list associated with this to-do item.
- ///
- public TodoList TodoList { get; set; } = null!;
+ ///
+ /// Foreign key referencing the associated to-do list.
+ ///
+ [ForeignKey(nameof(TodoList))]
+ public required string TodoListId { get; set; }
+ ///
+ /// Navigation property to the to-do list associated with this to-do item.
+ ///
+ public TodoList TodoList { get; set; } = null!;
- ///
- /// Collection of to-do items associated with this category.
- ///
- public virtual ICollection TodoItems { get; set; } = null!;
+ ///
+ /// Collection of to-do items associated with this category.
+ ///
+ public virtual ICollection TodoItems { get; set; } = null!;
- ///
- /// Maximum allowed length of .
- ///
- public const int NameMaxLength = 50;
+ ///
+ /// Maximum allowed length of .
+ ///
+ public const int NameMaxLength = 50;
}
diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs
index bd093e5..0dfd0d2 100644
--- a/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs
+++ b/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs
@@ -10,50 +10,50 @@ namespace AdvancedTodoList.Core.Models.TodoLists;
///
public class TodoList : IEntity, IHasOwner
{
- ///
- /// An unique identifier for the to-do list.
- ///
- [Key]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public string Id { get; set; } = null!;
+ ///
+ /// An unique identifier for the to-do list.
+ ///
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public string Id { get; set; } = null!;
- ///
- /// Name (title) of the to-do list.
- ///
- [MaxLength(NameMaxLength)]
- public required string Name { get; set; } = null!;
- ///
- /// Description of the to-do list.
- ///
- [MaxLength(DescriptionMaxLength)]
- public required string Description { get; set; } = null!;
+ ///
+ /// Name (title) of the to-do list.
+ ///
+ [MaxLength(NameMaxLength)]
+ public required string Name { get; set; } = null!;
+ ///
+ /// Description of the to-do list.
+ ///
+ [MaxLength(DescriptionMaxLength)]
+ public required string Description { get; set; } = null!;
- ///
- /// Foreign key referencing the user who created this to-do list.
- ///
- [ForeignKey(nameof(Owner))]
- public required string? OwnerId { get; set; } = null!;
- ///
- /// Navigation property to the user who created this to-do list.
- ///
- public ApplicationUser? Owner { get; set; }
+ ///
+ /// Foreign key referencing the user who created this to-do list.
+ ///
+ [ForeignKey(nameof(Owner))]
+ public required string? OwnerId { get; set; } = null!;
+ ///
+ /// Navigation property to the user who created this to-do list.
+ ///
+ public ApplicationUser? Owner { get; set; }
- ///
- /// Maximum allowed length of .
- ///
- public const int NameMaxLength = 100;
- ///
- /// Maximum allowed length of .
- ///
- public const int DescriptionMaxLength = 25_000;
+ ///
+ /// Maximum allowed length of .
+ ///
+ public const int NameMaxLength = 100;
+ ///
+ /// Maximum allowed length of .
+ ///
+ public const int DescriptionMaxLength = 25_000;
- ///
- /// Collection of to-do items associated with this todo list.
- ///
- public virtual ICollection TodoItems { get; set; } = null!;
+ ///
+ /// Collection of to-do items associated with this todo list.
+ ///
+ public virtual ICollection TodoItems { get; set; } = null!;
- ///
- /// Collection of list's members.
- ///
- public virtual ICollection TodoListMembers { get; set; } = null!;
+ ///
+ /// Collection of list's members.
+ ///
+ public virtual ICollection TodoListMembers { get; set; } = null!;
}
diff --git a/AdvancedTodoList.Core/Options/AccessTokenOptions.cs b/AdvancedTodoList.Core/Options/AccessTokenOptions.cs
deleted file mode 100644
index 5f34ae1..0000000
--- a/AdvancedTodoList.Core/Options/AccessTokenOptions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace AdvancedTodoList.Core.Options;
-
-///
-/// A class that contains access token options.
-///
-public class AccessTokenOptions
-{
- ///
- /// Valid audience of tokens.
- ///
- public string ValidAudience { get; set; } = null!;
-
- ///
- /// Valid issuer of tokens.
- ///
- public string ValidIssuer { get; set; } = null!;
-
- ///
- /// Seconds before token expires.
- ///
- public int ExpirationSeconds { get; set; }
-
- ///
- /// A secret key used for signing access tokens.
- ///
- public string SecretKey { get; set; } = null!;
-}
diff --git a/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs b/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs
deleted file mode 100644
index 8eae032..0000000
--- a/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace AdvancedTodoList.Core.Options;
-
-///
-/// A class that contains invitation link options.
-///
-public class InvitationLinkOptions
-{
- ///
- /// Size of the refresh token in bytes.
- ///
- public int Size { get; set; }
-
- ///
- /// Days before token expires.
- ///
- public int ExpirationDays { get; set; }
-}
diff --git a/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs b/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs
deleted file mode 100644
index 60e6925..0000000
--- a/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace AdvancedTodoList.Core.Options;
-
-///
-/// A class that contains refresh token options.
-///
-public class RefreshTokenOptions
-{
- ///
- /// Size of the refresh token in bytes.
- ///
- public int Size { get; set; }
-
- ///
- /// Days before token expires.
- ///
- public int ExpirationDays { get; set; }
-}
diff --git a/AdvancedTodoList.Core/Pagination/Page.cs b/AdvancedTodoList.Core/Pagination/Page.cs
index e937e08..e346790 100644
--- a/AdvancedTodoList.Core/Pagination/Page.cs
+++ b/AdvancedTodoList.Core/Pagination/Page.cs
@@ -10,20 +10,20 @@
/// The type of items contained in the page.
public class Page(IEnumerable items, int pageNumber, int pageSize, int totalCount)
{
- ///
- /// The collection of items in the current page.
- ///
- public IEnumerable Items { get; } = items;
- ///
- /// The page number.
- ///
- public int PageNumber { get; } = pageNumber;
- ///
- /// The size of the page (number of items per page).
- ///
- public int PageSize { get; } = pageSize;
- ///
- /// The total count of items across all pages.
- ///
- public int TotalCount { get; } = totalCount;
+ ///
+ /// The collection of items in the current page.
+ ///
+ public IEnumerable Items { get; } = items;
+ ///
+ /// The page number.
+ ///
+ public int PageNumber { get; } = pageNumber;
+ ///
+ /// The size of the page (number of items per page).
+ ///
+ public int PageSize { get; } = pageSize;
+ ///
+ /// The total count of items across all pages.
+ ///
+ public int TotalCount { get; } = totalCount;
}
diff --git a/AdvancedTodoList.Core/Pagination/PaginationParameters.cs b/AdvancedTodoList.Core/Pagination/PaginationParameters.cs
index efc346b..766c667 100644
--- a/AdvancedTodoList.Core/Pagination/PaginationParameters.cs
+++ b/AdvancedTodoList.Core/Pagination/PaginationParameters.cs
@@ -7,12 +7,12 @@
/// The number of items per page. Default is 20.
public record PaginationParameters(int Page = 1, int PageSize = 20)
{
- ///
- /// Minimum size of the page.
- ///
- public const int MinPageSize = 1;
- ///
- /// Maximum size of the page.
- ///
- public const int MaxPageSize = 100;
+ ///
+ /// Minimum size of the page.
+ ///
+ public const int MinPageSize = 1;
+ ///
+ /// Maximum size of the page.
+ ///
+ public const int MaxPageSize = 100;
}
diff --git a/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs b/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs
new file mode 100644
index 0000000..295d5f0
--- /dev/null
+++ b/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs
@@ -0,0 +1,25 @@
+using AdvancedTodoList.Core.Models;
+
+namespace AdvancedTodoList.Core.Repositories;
+
+///
+/// An interface for the service that checks whether an entity with an ID exists.
+///
+public interface IEntityExistenceChecker
+{
+ ///
+ /// Asynchronously checks whether an entity of type with an ID
+ /// of type exists.
+ ///
+ /// Type of the entity.
+ /// Type which ID of the entity has.
+ /// ID of the entity which existence is checked.
+ ///
+ /// A task representing the asynchronous operation. The task result contains
+ /// if entity with the given ID exists; otherwise
+ /// .
+ ///
+ Task ExistsAsync(TId id)
+ where TEntity : class, IEntity
+ where TId : IEquatable;
+}
diff --git a/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs b/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs
index 6d686b9..38d9c6d 100644
--- a/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs
+++ b/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs
@@ -4,13 +4,13 @@ namespace AdvancedTodoList.Core.Repositories;
public interface IInvitationLinksRepository : IRepository
{
- ///
- /// Finds an invintation link by its value asynchronously.
- ///
- /// Value of the link.
- ///
- /// A task representing asynchronous operation which contains requested link or
- /// it was not found.
- ///
- Task FindAsync(string linkValue);
+ ///
+ /// Finds an invintation link by its value asynchronously.
+ ///
+ /// Value of the link.
+ ///
+ /// A task representing asynchronous operation which contains requested link or
+ /// it was not found.
+ ///
+ Task FindAsync(string linkValue);
}
diff --git a/AdvancedTodoList.Core/Repositories/IRepository.cs b/AdvancedTodoList.Core/Repositories/IRepository.cs
index a1f06f9..e5c3436 100644
--- a/AdvancedTodoList.Core/Repositories/IRepository.cs
+++ b/AdvancedTodoList.Core/Repositories/IRepository.cs
@@ -10,57 +10,57 @@ namespace AdvancedTodoList.Core.Repositories;
/// The type of entity.
/// The type of entity's primary key.
public interface IRepository
- where TEntity : class, IEntity
- where TKey : IEquatable
+ where TEntity : class, IEntity
+ where TKey : IEquatable
{
- ///
- /// Asynchronously adds a new entity to the repository.
- ///
- /// The entity to add.
- Task AddAsync(TEntity entity);
+ ///
+ /// Asynchronously adds a new entity to the repository.
+ ///
+ /// The entity to add.
+ Task AddAsync(TEntity entity);
- ///
- /// Asynchronously retrieves an entity by its primary key.
- ///
- /// The primary key of the entity to retrieve.
- ///
- /// A task that represents an asynchronous operation and the entity if found;
- /// otherwise, .
- ///
- Task GetByIdAsync(TKey id);
+ ///
+ /// Asynchronously retrieves an entity by its primary key.
+ ///
+ /// The primary key of the entity to retrieve.
+ ///
+ /// A task that represents an asynchronous operation and the entity if found;
+ /// otherwise, .
+ ///
+ Task GetByIdAsync(TKey id);
- ///
- /// Asynchronously retrieves an aggregate by applying a specification.
- ///
- /// Type of the aggregate to retrieve.
- /// Specification to apply.
- ///
- /// A task that represents an asynchronous operation and the aggregate if found;
- /// otherwise, .
- ///
- Task GetAggregateAsync(ISpecification specification) where TDto : class;
+ ///
+ /// Asynchronously retrieves an aggregate by applying a specification.
+ ///
+ /// Type of the aggregate to retrieve.
+ /// Specification to apply.
+ ///
+ /// A task that represents an asynchronous operation and the aggregate if found;
+ /// otherwise, .
+ ///
+ Task GetAggregateAsync(ISpecification specification) where TDto : class;
- ///
- /// Gets a page with entities mapped to type asynchronously.
- ///
- /// Returned type of items on the page.
- /// Pagination parameters.
- /// Specification used for entities retrival.
- ///
- /// A task that represents an asynchronous operation and a page with entities mapped
- /// to type.
- ///
- Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification);
+ ///
+ /// Gets a page with entities mapped to type asynchronously.
+ ///
+ /// Returned type of items on the page.
+ /// Pagination parameters.
+ /// Specification used for entities retrival.
+ ///
+ /// A task that represents an asynchronous operation and a page with entities mapped
+ /// to type.
+ ///
+ Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification);
- ///
- /// Asynchronously updates an existing entity in the repository.
- ///
- /// The entity to update.
- Task UpdateAsync(TEntity entity);
+ ///
+ /// Asynchronously updates an existing entity in the repository.
+ ///
+ /// The entity to update.
+ Task UpdateAsync(TEntity entity);
- ///
- /// Asynchronously deletes an entity from the repository.
- ///
- /// The entity to delete.
- Task DeleteAsync(TEntity entity);
+ ///
+ /// Asynchronously deletes an entity from the repository.
+ ///
+ /// The entity to delete.
+ Task DeleteAsync(TEntity entity);
}
diff --git a/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs b/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs
index 32fa72d..791b4d8 100644
--- a/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs
+++ b/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs
@@ -7,13 +7,13 @@ namespace AdvancedTodoList.Core.Repositories;
///
public interface ITodoListMembersRepository : IRepository
{
- ///
- /// Finds a to-do list member by to-do list ID and user's ID asynchronously.
- ///
- /// ID of the user.
- /// ID of the to-do list.
- ///
- /// Found to-do list member, or if it was not found.
- ///
- Task FindAsync(string todoListId, string userId);
+ ///
+ /// Finds a to-do list member by to-do list ID and user's ID asynchronously.
+ ///
+ /// ID of the user.
+ /// ID of the to-do list.
+ ///
+ /// Found to-do list member, or if it was not found.
+ ///
+ Task FindAsync(string todoListId, string userId);
}
diff --git a/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs b/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs
index 177b096..d452e12 100644
--- a/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs
+++ b/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs
@@ -5,21 +5,21 @@
///
public interface IUnitOfWork
{
- ///
- /// Begins a new transaction asynchronously.
- ///
- /// A task representing the asynchronous operation.
- Task BeginTransactionAsync();
+ ///
+ /// Begins a new transaction asynchronously.
+ ///
+ /// A task representing the asynchronous operation.
+ Task BeginTransactionAsync();
- ///
- /// Commits the transaction asynchronously.
- ///
- /// A task representing the asynchronous operation.
- Task CommitAsync();
+ ///
+ /// Commits the transaction asynchronously.
+ ///
+ /// A task representing the asynchronous operation.
+ Task CommitAsync();
- ///
- /// Rolls back the transaction asynchronously.
- ///
- /// A task representing the asynchronous operation.
- Task RollbackAsync();
+ ///
+ /// Rolls back the transaction asynchronously.
+ ///
+ /// A task representing the asynchronous operation.
+ Task RollbackAsync();
}
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs b/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs
index 9bc813f..8320f1b 100644
--- a/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs
+++ b/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs
@@ -7,13 +7,13 @@ namespace AdvancedTodoList.Core.Repositories;
///
public interface IUserRefreshTokensRepository : IRepository
{
- ///
- /// Finds user's refresh token by user's ID and refresh token's value asynchronously.
- ///
- /// User's unique identifier
- /// Value of the refresh token.
- ///
- /// Found user's refresh token, or if it was not found.
- ///
- Task FindAsync(string userId, string refreshToken);
+ ///
+ /// Finds user's refresh token by user's ID and refresh token's value asynchronously.
+ ///
+ /// User's unique identifier
+ /// Value of the refresh token.
+ ///
+ /// Found user's refresh token, or if it was not found.
+ ///
+ Task FindAsync(string userId, string refreshToken);
}
diff --git a/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs b/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs
deleted file mode 100644
index 1d7c0c8..0000000
--- a/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using AdvancedTodoList.Core.Models.Auth;
-
-namespace AdvancedTodoList.Core.Services.Auth;
-
-///
-/// An interface for a service that manages access tokens.
-///
-public interface IAccessTokensService
-{
- ///
- /// Generates an access token for the user.
- ///
- /// User which will receive an access token.
- ///
- /// A string that represents an access token.
- ///
- string GenerateAccessToken(ApplicationUser user);
-
- ///
- /// Validates an access token without checking expiration time and then returns
- /// ID of the user stored in it asynchronously.
- ///
- /// A string that represents an access token.
- ///
- /// A user ID retrieved from the access token or , if validation failed.
- ///
- Task GetUserIdFromExpiredTokenAsync(string accessToken);
-}
diff --git a/AdvancedTodoList.Core/Services/Auth/IAuthService.cs b/AdvancedTodoList.Core/Services/Auth/IAuthService.cs
deleted file mode 100644
index 0715f89..0000000
--- a/AdvancedTodoList.Core/Services/Auth/IAuthService.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-
-namespace AdvancedTodoList.Core.Services.Auth;
-
-///
-/// Interface for service that performs authentication operations.
-///
-public interface IAuthService
-{
- ///
- /// Logs a user in asynchronously.
- ///
- /// Data required for logging in.
- ///
- /// Returns a task representing the asynchronous operation, containing a
- /// response with access and refresh tokens or null if authorization fails.
- ///
- Task LogInAsync(LogInDto logInDto);
-
- ///
- /// Registers a new user asynchronously.
- ///
- /// Data required for user registration.
- ///
- /// Returns a task representing the asynchronous operation, containing the registration result.
- ///
- Task RegisterAsync(RegisterDto registerDto);
-
- ///
- /// Refreshes the access token asynchronously.
- ///
- /// Data required for token refresh.
- ///
- /// Returns a task representing the asynchronous operation,
- /// containing a response with access and refresh tokens or null if authorization fails.
- ///
- Task RefreshAsync(RefreshDto refreshDto);
-
- ///
- /// Logs a user out asynchronously by revoking a refresh token.
- ///
- /// ID of the caller.
- /// Data required for logging out.
- ///
- /// Returns a task representing the asynchronous operation,
- /// indicating the success or failure of the operation.
- ///
- Task LogOutAsync(string userId, LogOutDto logOutDto);
-}
diff --git a/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs b/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs
deleted file mode 100644
index 153adaf..0000000
--- a/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using AdvancedTodoList.Core.Models;
-using AdvancedTodoList.Core.Models.TodoLists.Members;
-
-namespace AdvancedTodoList.Core.Services.Auth;
-
-///
-/// Interface for a service that checks user's permissions.
-///
-public interface IPermissionsChecker
-{
- ///
- /// Asynchronously checks whether the user is a member of the to-do list with
- /// specified ID.
- ///
- /// To-do list context.
- ///
- /// if user is a member of the list; otherwise .
- ///
- Task IsMemberOfListAsync(TodoListContext context);
-
- ///
- /// Asynchronously checks whether the user is a member of the to-do list and
- /// has a permission defined by the funciton .
- ///
- /// To-do list context.
- /// Function that should return if user has required permission.
- ///
- /// if user is a member of the list and has required permission;
- /// otherwise .
- ///
- Task HasPermissionAsync(TodoListContext context, Func permission);
-
- ///
- /// Asynchronously checks whether the user can touch an entity.
- ///
- ///
- /// This method firstly checks whether implements
- /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list;
- /// otherwise the method checks if user has the permission defined by the function .
- ///
- /// Type of the entity.
- /// Type of the unique identifier used by the entity.
- /// To-do list context.
- /// ID of the entity.
- /// Function that should return if user has required permission.
- ///
- /// if user is either an owner of the entity and a member of a to-do list,
- /// or he/she/they has permission defined by ; otherwise .
- ///
- Task CanTouchEntityAsync(TodoListContext context, TEntity entity,
- Func permission)
- where TEntity : class, IEntity
- where TKey : IEquatable;
-
- ///
- /// Asynchronously checks whether the user has a permission to change the role
- /// with the priority of .
- ///
- /// To-do list context.
- /// ID of the role.
- /// Function that should return if user has required permission.
- ///
- /// if user has and highest role priority than
- /// the ; otherwise .
- ///
- Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority,
- Func permission);
-}
diff --git a/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs b/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs
deleted file mode 100644
index e1aaddb..0000000
--- a/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace AdvancedTodoList.Core.Services.Auth;
-
-///
-/// An interface for a service that manages refresh tokens.
-///
-public interface IRefreshTokensService
-{
- ///
- /// Generates a refresh token for the user and saves it asynchronously.
- ///
- /// ID of the user who will receive the token.
- ///
- /// A string that represents a refresh token or if user does not exist.
- ///
- Task GenerateAsync(string userId);
-
- ///
- /// Revokes the refresh token of the user asynchronously
- ///
- /// ID of the user whose token is being revoked.
- /// Value of the token to be revoked.
- ///
- /// on success; otherwise.
- ///
- Task RevokeAsync(string userId, string token);
-
- ///
- /// Checks whether refresh token is valid asynchronously.
- ///
- /// ID of the user whose token is being validated.
- /// Value of the token to be validated.
- ///
- /// if token is valid;
- /// otherwise.
- ///
- Task ValidateAsync(string userId, string token);
-}
diff --git a/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs b/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs
deleted file mode 100644
index ccffe4e..0000000
--- a/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace AdvancedTodoList.Core.Services.Auth;
-
-///
-/// Class that represents a result of register operation.
-///
-public class RegisterResult
-{
- ///
- /// Flag which determines whether result reports succeess.
- ///
- public bool IsSuccess { get; private init; }
- ///
- /// Error messages.
- ///
- public IEnumerable Errors { get; private init; }
-
- private RegisterResult(bool isSuccess, IEnumerable errors)
- {
- IsSuccess = isSuccess;
- Errors = errors;
- }
-
- ///
- /// Returns a result that reports success.
- ///
- ///
- /// A result that reports success.
- ///
- public static RegisterResult Success() => new(true, Array.Empty());
-
- ///
- /// Returns a result that reports failure.
- ///
- /// Error messages.
- ///
- /// A result that reports failure.
- ///
- public static RegisterResult Failure(IEnumerable errors) => new(false, errors);
-}
-
-///
-/// Record that represents a register error.
-///
-/// Property that caused an error or '$' if error was not caused by a property.
-/// An error message.
-public record RegisterError(string Property, string Message);
diff --git a/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs b/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs
deleted file mode 100644
index 1893c06..0000000
--- a/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using AdvancedTodoList.Core.Models;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// An interface for the service that checks whether an entity with an ID exists.
-///
-public interface IEntityExistenceChecker
-{
- ///
- /// Asynchronously checks whether an entity of type with an ID
- /// of type exists.
- ///
- /// Type of the entity.
- /// Type which ID of the entity has.
- /// ID of the entity which existence is checked.
- ///
- /// A task representing the asynchronous operation. The task result contains
- /// if entity with the given ID exists; otherwise
- /// .
- ///
- Task ExistsAsync(TId id)
- where TEntity : class, IEntity
- where TId : IEquatable;
-}
diff --git a/AdvancedTodoList.Core/Services/IInvitationLinksService.cs b/AdvancedTodoList.Core/Services/IInvitationLinksService.cs
deleted file mode 100644
index 9e9da10..0000000
--- a/AdvancedTodoList.Core/Services/IInvitationLinksService.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// An interface for a service that manages invitation links.
-///
-public interface IInvitationLinksService
-{
- ///
- /// Joins the caller to the to-do list by invitation list asynchronously.
- ///
- /// ID of the caller.
- /// Invitation link to use.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- Task JoinAsync(string callerId, string invitationLinkValue);
-
- ///
- /// Gets invitation links associated with the to-do list asynchronously.
- ///
- /// To-do list context of the operation.
- /// Pagination parameters to use.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- Task>> GetInvitationLinksAsync(TodoListContext context,
- PaginationParameters parameters);
-
- ///
- /// Creates an invitation link associated to the to-do list asynchronously.
- ///
- /// To-do list context.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- Task> CreateAsync(TodoListContext context);
-
- ///
- /// Deletes an invitation link associted to the to-do list asynchronously.
- ///
- /// To-do list context.
- /// ID of the link.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- Task DeleteAsync(TodoListContext context, int linkId);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs b/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs
deleted file mode 100644
index 888fcfb..0000000
--- a/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Interface for a service that manages to-do list items categories.
-///
-public interface ITodoItemCategoriesService
-{
- ///
- /// Asynchronously checks whether the category ID is valid for the given context.
- ///
- ///
- /// ID is considered as valid.
- ///
- /// To-do list context.
- /// ID of the category to validate.
- ///
- /// A task representing the asynchronous operation.
- /// if ID is valid, otherwise.
- ///
- public Task IsCategoryValidForContext(TodoListContext context, int? categoryId);
-
- ///
- /// Retrieves a page of to-do list items categories of the list with the specified ID.
- ///
- /// To-do list context.
- /// Pagination parameters to use.
- /// Optional name to filter categories by.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task>> GetCategoriesOfListAsync(
- TodoListContext context, PaginationParameters paginationParameters, string? name = null);
-
- ///
- /// Retrieves a to-do list item category by its ID asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to retrieve.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> GetByIdAsync(TodoListContext context, int categoryId);
-
- ///
- /// Creates a new to-do list item category asynchronously.
- ///
- /// To-do list context.
- /// The DTO containing information for creating the to-do list item.
- /// ID of the user who creates the to-do list item.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto);
-
- ///
- /// Edits a to-do list item category asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to edit.
- /// The DTO containing information for editing the to-do list item.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto);
-
- ///
- /// Deletes a to-do list item category asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to delete.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task DeleteAsync(TodoListContext context, int categoryId);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoItemsService.cs b/AdvancedTodoList.Core/Services/ITodoItemsService.cs
deleted file mode 100644
index f338721..0000000
--- a/AdvancedTodoList.Core/Services/ITodoItemsService.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-using AdvancedTodoList.Core.Specifications;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Interface for a service that manages to-do list items.
-///
-public interface ITodoItemsService
-{
- ///
- /// Retrieves a page of to-do list items of the list with the specified ID.
- ///
- /// To-do list context.
- /// Pagination parameters to use.
- /// Filter parameters to apply.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task>> GetItemsOfListAsync(
- TodoListContext context, PaginationParameters paginationParameters, TodoItemsFilter filter);
-
- ///
- /// Retrieves a to-do list item by its ID asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to retrieve.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> GetByIdAsync(TodoListContext context, int itemId);
-
- ///
- /// Creates a new to-do list item asynchronously.
- ///
- /// To-do list context.
- /// The DTO containing information for creating the to-do list item.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task CreateAsync(TodoListContext context, TodoItemCreateDto dto);
-
- ///
- /// Edits a to-do list item asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to edit.
- /// The DTO containing information for editing the to-do list item.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto);
-
- ///
- /// Updates the state of a to-do list item asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to update the state.
- /// The DTO which contains the state of the to-do item to set.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto);
-
- ///
- /// Deletes a to-do list item asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list item to delete.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task DeleteAsync(TodoListContext context, int itemId);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs b/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs
deleted file mode 100644
index 2c33383..0000000
--- a/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using AdvancedTodoList.Core.Models;
-using AdvancedTodoList.Core.Models.TodoLists;
-using AdvancedTodoList.Core.Models.TodoLists.Members;
-using AdvancedTodoList.Core.Pagination;
-using AdvancedTodoList.Core.Specifications;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// An interface that represents a service for perfoming CRUD operations
-/// on to-do list dependant entities and mapping/unmapping DTOs.
-///
-/// Type of the to-do list dependant entity.
-/// Type of the unique identifier used by the entity.
-public interface ITodoListDependantEntitiesService
- where TEntity : class, IEntity, ITodoListDependant
- where TKey : IEquatable
-{
- ///
- /// Retrieves a page of to-do list dependant entities mapped to .
- ///
- ///
- /// This method checks if to-do list ID is valid, but doesn't filter by it.
- /// Filtering should be done in .
- ///
- /// To-do list context.
- /// Specification to apply to entities.
- /// Pagination parameters to use.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation and the requested page on success.
- ///
- public Task>> GetPageAsync(TodoListContext context,
- ISpecification specification, PaginationParameters paginationParameters);
-
- ///
- /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to .
- ///
- /// To-do list context.
- /// The ID of the entity to retrieve.
- /// DTO to map entity to.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation and the requested dto on success.
- ///
- Task> GetByIdAsync(TodoListContext context, TKey entityId) where TDto : class;
-
- ///
- /// Creates a new a to-do list dependant entity asynchronously from the DTO.
- ///
- ///
- /// If implements the interface, then
- /// this method will set the caller as an owner.
- ///
- /// To-do list context.
- /// The DTO containing information for creating the entity.
- /// Optional accessor for the permission required for the user to perform the action.
- ///
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation and the created mapped to
- /// on success.
- ///
- public Task> CreateAsync(
- TodoListContext context, TInputDto dto, Func? permission = null)
- where TOutputDto : class;
-
- ///
- /// Edits a to-do list dependant entity asynchronously.
- ///
- /// To-do list context.
- /// The ID of the entity to edit.
- /// The DTO containing information for editing the entity.
- /// Optional accessor for the permission required for the user to perform the action.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- public Task UpdateAsync(TodoListContext context, TKey entityId,
- TDto dto, Func? permission = null);
-
- ///
- /// Deletes a to-do list dependant entity asynchronously.
- ///
- /// To-do list context.
- /// The ID of the entity to delete.
- /// Optional accessor for the permission required for the user to perform the action.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- public Task DeleteAsync(TodoListContext context,
- TKey entityId, Func? permission = null);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoListMembersService.cs b/AdvancedTodoList.Core/Services/ITodoListMembersService.cs
deleted file mode 100644
index 29c07a3..0000000
--- a/AdvancedTodoList.Core/Services/ITodoListMembersService.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-using AdvancedTodoList.Core.Specifications;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Interface for a service that manages to-do list members.
-///
-public interface ITodoListMembersService
-{
- ///
- /// Gets a page with members of a to-do list asynchronously.
- ///
- /// To-do list context.
- /// Pagination parameters to use.
- /// Filter parameters to use.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- Task>> GetMembersAsync(TodoListContext context,
- PaginationParameters paginationParameters, TodoListMembersFilter filter);
-
- ///
- /// Adds a member to a to-do list asynchronously.
- ///
- /// To-do list context.
- /// DTO that contains information needed for adding a member. Supossed to be valid.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto);
-
- ///
- /// Updates a role of the member of a to-do list asynchronously.
- ///
- /// To-do list context.
- /// ID of the member.
- /// DTO that contains information needed for updating a role.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- Task UpdateMemberRoleAsync(TodoListContext context,
- int memberId, TodoListMemberUpdateRoleDto dto);
-
- ///
- /// Removes a member from a to-do list asynchronously.
- ///
- /// To-do list context.
- /// ID of the member.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- Task RemoveMemberAsync(TodoListContext context, int memberId);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoListRolesService.cs b/AdvancedTodoList.Core/Services/ITodoListRolesService.cs
deleted file mode 100644
index 88030be..0000000
--- a/AdvancedTodoList.Core/Services/ITodoListRolesService.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Interface for a service that manages to-do list role.
-///
-public interface ITodoListRolesService
-{
- ///
- /// Retrieves a page of to-do list roles of the list with the specified ID.
- ///
- /// To-do list context.
- /// Pagination parameters to use.
- /// Optional name to filter categories by.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task>> GetRolesOfListAsync(
- TodoListContext context, PaginationParameters paginationParameters, string? name = null);
-
- ///
- /// Retrieves a to-do list role by its ID asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list role to retrieve.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> GetByIdAsync(TodoListContext context, int roleId);
-
- ///
- /// Creates a new to-do list role asynchronously.
- ///
- /// To-do list context.
- /// The DTO containing information for creating the to-do list role.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto);
-
- ///
- /// Edits a to-do list role asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list role to edit.
- /// The DTO containing information for editing the to-do list role.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto);
-
- ///
- /// Deletes a to-do list role asynchronously.
- ///
- /// To-do list context.
- /// The ID of the to-do list role to delete.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task DeleteAsync(TodoListContext context, int roleId);
-}
diff --git a/AdvancedTodoList.Core/Services/ITodoListsService.cs b/AdvancedTodoList.Core/Services/ITodoListsService.cs
deleted file mode 100644
index ad3be55..0000000
--- a/AdvancedTodoList.Core/Services/ITodoListsService.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Pagination;
-using AdvancedTodoList.Core.Specifications;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Interface for a service that manages to-do lists.
-///
-public interface ITodoListsService
-{
- ///
- /// Retrieves a page of to-do lists, with the requirement that the user
- /// is a member of those lists.
- ///
- /// Id of the user
- /// Pagination parameters to use.
- /// Filter parameters to apply.
- ///
- /// A task representing the asynchronous operation containing the result of operation.
- ///
- public Task> GetListsOfUserAsync(string userId,
- PaginationParameters paginationParameters, TodoListsFilter filter);
-
- ///
- /// Retrieves a to-do list by its ID asynchronously.
- ///
- /// To-do list context.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- public Task> GetByIdAsync(TodoListContext context);
-
- ///
- /// Creates a new to-do list asynchronously.
- ///
- ///
- /// This method should also create an "Owner" role with all permissions and assign the caller to it.
- ///
- /// The DTO containing information for creating the to-do list.
- /// ID of the user who creates the to-do list.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a created model mapped to .
- ///
- public Task CreateAsync(TodoListCreateDto dto, string callerId);
-
- ///
- /// Edits a to-do list asynchronously.
- ///
- /// To-do list context.
- /// The DTO containing information for editing the to-do list.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- public Task EditAsync(TodoListContext context, TodoListCreateDto dto);
-
- ///
- /// Deletes a to-do list asynchronously.
- ///
- /// To-do list context.
- ///
- /// A task representing the asynchronous operation. The task contains
- /// a result of the operation.
- ///
- public Task DeleteAsync(TodoListContext context);
-}
diff --git a/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs b/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs
deleted file mode 100644
index 0da518a..0000000
--- a/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Represents possible results of the join to-do list by invitatation link operation.
-///
-public class JoinByInvitationLinkResult(JoinByInvitationLinkStatus status, TodoListMemberMinimalViewDto? dto = null)
-{
- ///
- /// Status of the operation.
- ///
- public JoinByInvitationLinkStatus Status { get; } = status;
-
- ///
- /// Gets additional DTO of the member, can be .
- ///
- public TodoListMemberMinimalViewDto? Dto { get; } = dto;
-}
-
-///
-/// Enum that represents possible result statuses of the join to-do list by invitatation link operation.
-///
-public enum JoinByInvitationLinkStatus
-{
- ///
- /// Operation was successfull.
- ///
- Success,
- ///
- /// Invitation link was not found.
- ///
- NotFound,
- ///
- /// Invitation link is expired.
- ///
- Expired,
- ///
- /// User is already a member of the to-do list.
- ///
- UserIsAlreadyMember
-}
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Services/ServiceResponse.cs b/AdvancedTodoList.Core/Services/ServiceResponse.cs
deleted file mode 100644
index b180319..0000000
--- a/AdvancedTodoList.Core/Services/ServiceResponse.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Represent a generic service response which indicates an operation status and
-/// contains an additonal information.
-///
-/// Type of the content saved in the response.
-public class ServiceResponse(ServiceResponseStatus status, T? result = default)
-{
- ///
- /// Gets the status of operation.
- ///
- public ServiceResponseStatus Status { get; } = status;
-
- ///
- /// Gets the result of the operation.
- ///
- public T? Result { get; } = result;
-}
-
-///
-/// Represents a possible service response status.
-///
-public enum ServiceResponseStatus
-{
- ///
- /// Status that indicates success.
- ///
- Success,
- ///
- /// Status that indicates that entity could not be found or the caller isn't
- /// suppossed to know that entity exists.
- ///
- NotFound,
- ///
- /// Status that indicates that user has no permission to perform the operation.
- ///
- Forbidden
-}
diff --git a/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs b/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs
deleted file mode 100644
index c3371dc..0000000
--- a/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-
-namespace AdvancedTodoList.Core.Services;
-
-public class TodoItemsServiceResponse(TodoItemsServiceStatus status, TodoItemGetByIdDto? result = null)
-{
- ///
- /// Status of the operation.
- ///
- public TodoItemsServiceStatus Status { get; set; } = status;
-
- ///
- /// Result of the operation.
- ///
- public TodoItemGetByIdDto? Result { get; set; } = result;
-}
-
-public enum TodoItemsServiceStatus
-{
- ///
- /// Status that indicates success.
- ///
- Success,
- ///
- /// Status that indicates that entity could not be found or the caller isn't
- /// suppossed to know that entity exists.
- ///
- NotFound,
- ///
- /// Status that indicates that user has no permission to perform the operation.
- ///
- Forbidden,
- ///
- /// Status that indicates that user provided an invalid category ID.
- ///
- InvalidCategoryId
-}
\ No newline at end of file
diff --git a/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs b/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs
deleted file mode 100644
index 782c389..0000000
--- a/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-
-namespace AdvancedTodoList.Core.Services;
-
-///
-/// Class that represents possible results of the method .
-///
-public class AddTodoListMemberServiceResult(
- TodoListMemberServiceResultStatus status, TodoListMemberMinimalViewDto? dto = null)
-{
- ///
- /// Status of the operation.
- ///
- public TodoListMemberServiceResultStatus Status { get; } = status;
-
- ///
- /// Gets additional DTO of the member, can be .
- ///
- public TodoListMemberMinimalViewDto? Dto { get; } = dto;
-}
-
-///
-/// Enum that represents possible result statuses of the to-do list members service.
-///
-public enum TodoListMemberServiceResultStatus
-{
- ///
- /// Operation was successfull.
- ///
- Success,
- ///
- /// To-do list was not found.
- ///
- NotFound,
- ///
- /// User is already a member of the to-do list, returned only in the add member method.
- ///
- UserAlreadyAdded,
- ///
- /// Role either doesn't exist or it's invalid for the current to-do list.
- ///
- InvalidRoleId,
- ///
- /// User has no permission to perform the operation.
- ///
- Forbidden
-}
diff --git a/AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs
similarity index 72%
rename from AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs
rename to AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs
index 26d47f8..0a227ea 100644
--- a/AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs
+++ b/AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs
@@ -1,6 +1,6 @@
using AdvancedTodoList.Core.Models.TodoLists;
-namespace AdvancedTodoList.Core.Specifications;
+namespace AdvancedTodoList.Core.Specifications.Filters;
///
/// Parameters for filtering to-do lists items.
@@ -17,7 +17,7 @@ namespace AdvancedTodoList.Core.Specifications;
/// Optional min deadline date to filter by.
/// Optional max deadline date to filter by.
public record TodoItemsFilter(
- string? Name = null, string? OwnerId = null,
- IEnumerable? State = null, IEnumerable? CategoryId = null,
- int? MinPriority = null, int? MaxPriority = null,
- DateTime? MinDeadlineDate = null, DateTime? MaxDeadlineDate = null);
+ string? Name = null, string? OwnerId = null,
+ IEnumerable? State = null, IEnumerable? CategoryId = null,
+ int? MinPriority = null, int? MaxPriority = null,
+ DateTime? MinDeadlineDate = null, DateTime? MaxDeadlineDate = null);
diff --git a/AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs
similarity index 84%
rename from AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs
rename to AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs
index b6739c9..55504b6 100644
--- a/AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs
+++ b/AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Specifications;
+namespace AdvancedTodoList.Core.Specifications.Filters;
///
/// Parameters for filtering to-do lists members.
@@ -14,4 +14,4 @@
/// Entries which have this substring in the '{FirstName} {LastName}' will be returned.
///
public record TodoListMembersFilter(IEnumerable? RoleId = null, string? UserId = null,
- string? UserName = null, string? FullName = null);
+ string? UserName = null, string? FullName = null);
diff --git a/AdvancedTodoList.Core/Specifications/TodoListsFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs
similarity index 81%
rename from AdvancedTodoList.Core/Specifications/TodoListsFilter.cs
rename to AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs
index dbb5e7a..8581b42 100644
--- a/AdvancedTodoList.Core/Specifications/TodoListsFilter.cs
+++ b/AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs
@@ -1,4 +1,4 @@
-namespace AdvancedTodoList.Core.Specifications;
+namespace AdvancedTodoList.Core.Specifications.Filters;
///
/// Parameters for filtering to-do lists.
diff --git a/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs b/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs
new file mode 100644
index 0000000..9f23856
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs
@@ -0,0 +1,38 @@
+using AdvancedTodoList.Core.Models;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications;
+
+///
+/// Represents a specification that defines criteria for obtaining entities by ID.
+///
+/// The type of entity.
+/// The type of the entity unique identifier.
+/// The unique identifier to filter by.
+public class GetByIdSpecification(TKey id) : ISpecification
+ where TEntity : class, IEntity
+ where TKey : IEquatable
+{
+ ///
+ /// The unique identifier to filter by.
+ ///
+ public TKey Id { get; } = id;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ /// Filters by ID provided in the constructor when not overriden.
+ ///
+ public Expression> Criteria => x => x.Id.Equals(Id);
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ /// Is empty when not overriden.
+ ///
+ public virtual List>> Includes => [];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ /// Is empty when not overriden.
+ ///
+ public virtual List IncludeStrings => [];
+}
diff --git a/AdvancedTodoList.Core/Specifications/ISpecification.cs b/AdvancedTodoList.Core/Specifications/ISpecification.cs
index ab48e38..cbec074 100644
--- a/AdvancedTodoList.Core/Specifications/ISpecification.cs
+++ b/AdvancedTodoList.Core/Specifications/ISpecification.cs
@@ -8,18 +8,18 @@ namespace AdvancedTodoList.Core.Specifications;
/// The type of entity.
public interface ISpecification
{
- ///
- /// Gets the criteria expression that defines the filtering conditions.
- ///
- Expression> Criteria { get; }
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ Expression> Criteria { get; }
- ///
- /// Gets the list of include expressions specifying related entities to be included in the query results.
- ///
- List>> Includes { get; }
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ ///
+ List>> Includes { get; }
- ///
- /// Gets the list of include strings specifying related entities to be included in the query results.
- ///
- List IncludeStrings { get; }
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ List IncludeStrings { get; }
}
diff --git a/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs b/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs
new file mode 100644
index 0000000..8e09a01
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs
@@ -0,0 +1,31 @@
+using AdvancedTodoList.Core.Models.TodoLists;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications;
+
+///
+/// Represents a specification that defines criteria for filtering invitation links.
+///
+/// ID of the list invitation links of which will be obtained.
+public class InvitationLinksSpecification(string todoListId) : ISpecification
+{
+ ///
+ /// Gets the ID of the to-do list to filter entities by.
+ ///
+ public string? TodoListId { get; } = todoListId;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ public virtual Expression> Criteria => x => x.TodoListId == TodoListId;
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ ///
+ public virtual List>> Includes { get; init; } = [];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ public virtual List IncludeStrings { get; init; } = [];
+}
diff --git a/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs b/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs
new file mode 100644
index 0000000..02f6164
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs
@@ -0,0 +1,41 @@
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications;
+
+///
+/// Represents a specification for obtaining an aggregate containg the to-do list member
+/// and his/her/their role, used in .
+///
+/// ID of the to-do list.
+/// ID of the user.
+public class MemberPermissionsSpecification(string todoListId, string userId) : ISpecification
+{
+ ///
+ /// Gets the to-do list ID.
+ ///
+ public string TodoListId { get; } = todoListId;
+ ///
+ /// Gets the user ID.
+ ///
+ public string UserId { get; } = userId;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ public Expression> Criteria =>
+ x => x.TodoListId == TodoListId && x.UserId == UserId;
+
+ ///
+ /// Gets the list of include expressions specifying a to-do list role to be included in the query results.
+ ///
+ public List>> Includes =>
+ [
+ x => x.Role
+ ];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ public List IncludeStrings { get; } = [];
+}
diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs
similarity index 56%
rename from AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs
rename to AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs
index bcca4a5..1218c9b 100644
--- a/AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs
@@ -1,7 +1,7 @@
using AdvancedTodoList.Core.Models.TodoLists;
using System.Linq.Expressions;
-namespace AdvancedTodoList.Infrastructure.Specifications;
+namespace AdvancedTodoList.Core.Specifications.Todo;
///
/// Represents a specification used for obtaining a to-do list item aggregate.
@@ -9,12 +9,12 @@ namespace AdvancedTodoList.Infrastructure.Specifications;
/// The unique identifier of the to-do list item to obtain.
public class TodoItemAggregateSpecification(int id) : GetByIdSpecification(id)
{
- ///
- /// Gets the list of include expressions specifying an owner.
- ///
- public override List>> Includes =>
- [
- x => x.Owner,
- x => x.Category
- ];
+ ///
+ /// Gets the list of include expressions specifying an owner.
+ ///
+ public override List>> Includes =>
+ [
+ x => x.Owner,
+ x => x.Category
+ ];
}
diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs
new file mode 100644
index 0000000..54b223b
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs
@@ -0,0 +1,59 @@
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Specifications.Filters;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications.Todo;
+
+///
+/// Represents a specification that defines criteria for filtering to-do list members
+/// and adds includes for users and roles models.
+///
+/// ID of the to-do list items of which will be obtained.
+/// Filter parameters.
+public class TodoItemsSpecification(string todoListId, TodoItemsFilter filter) :
+ ISpecification
+{
+ ///
+ /// Gets the ID of the to-do list to filter items by.
+ ///
+ public string? TodoListId { get; } = todoListId;
+ ///
+ /// Gets the filter parameters.
+ ///
+ public TodoItemsFilter Filter { get; } = filter;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ public Expression> Criteria => x =>
+ // Filter by to-do list
+ x.TodoListId == TodoListId &&
+ // Filter by name
+ (Filter.Name == null || x.Name.Contains(Filter.Name)) &&
+ // Filter by owner's ID
+ (Filter.OwnerId == null || x.OwnerId == Filter.OwnerId) &&
+ // Filter by states
+ (Filter.State == null || !Filter.State.Any() || Filter.State.Contains(x.State)) &&
+ // Filter by categories
+ (Filter.CategoryId == null || !Filter.CategoryId.Any() || Filter.CategoryId.Contains(x.CategoryId)) &&
+ // Filter by priority
+ (Filter.MinPriority == null || x.Priority >= Filter.MinPriority) &&
+ (Filter.MaxPriority == null || x.Priority <= Filter.MaxPriority) &&
+ // Filter by deadline date
+ (Filter.MinDeadlineDate == null || x.DeadlineDate >= Filter.MinDeadlineDate) &&
+ (Filter.MaxDeadlineDate == null || x.DeadlineDate <= Filter.MaxDeadlineDate);
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ ///
+ public List>> Includes =>
+ [
+ x => x.Owner,
+ x => x.Category
+ ];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ public List IncludeStrings => [];
+}
diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs
similarity index 58%
rename from AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs
rename to AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs
index 5aab3e5..d639fe9 100644
--- a/AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs
@@ -1,7 +1,7 @@
using AdvancedTodoList.Core.Models.TodoLists;
using System.Linq.Expressions;
-namespace AdvancedTodoList.Infrastructure.Specifications;
+namespace AdvancedTodoList.Core.Specifications.Todo;
///
/// Represents a specification used for obtaining a to-do list aggregate.
@@ -9,11 +9,11 @@ namespace AdvancedTodoList.Infrastructure.Specifications;
/// The unique identifier of the to-do list to obtain.
public class TodoListAggregateSpecification(string id) : GetByIdSpecification(id)
{
- ///
- /// Gets the list of include expressions specifying an owner.
- ///
- public override List>> Includes =>
- [
- x => x.Owner
- ];
+ ///
+ /// Gets the list of include expressions specifying an owner.
+ ///
+ public override List>> Includes =>
+ [
+ x => x.Owner
+ ];
}
diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs
new file mode 100644
index 0000000..e02f5d7
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs
@@ -0,0 +1,45 @@
+using AdvancedTodoList.Core.Models;
+using AdvancedTodoList.Core.Models.TodoLists;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications.Todo;
+
+///
+/// Represents a specification that defines criteria for filtering any todo-list dependant entities with name.
+///
+/// ID of the list items of which will be obtained.
+///
+/// Optional name to filter by.
+/// Entries which have this substring in the name will be returned.
+///
+public class TodoListDependantEntitiesSpecification(string todoListId, string? name = null) : ISpecification
+ where TEntity : ITodoListDependant, IHasName
+{
+ ///
+ /// Gets the ID of the to-do list to filter entities by.
+ ///
+ public string? TodoListId { get; } = todoListId;
+ ///
+ /// Gets the name to filter entities, which implement by.
+ ///
+ public string? Name { get; } = name;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ /// Filters only by the to-do list ID and the name when not overriden.
+ ///
+ public virtual Expression> Criteria => x =>
+ x.TodoListId == TodoListId && (Name == null || x.Name.Contains(Name));
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ /// Is empty when not overriden.
+ ///
+ public virtual List>> Includes { get; init; } = [];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ /// Is empty when not overriden.
+ ///
+ public virtual List IncludeStrings { get; init; } = [];
+}
diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs
new file mode 100644
index 0000000..3ddde03
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs
@@ -0,0 +1,51 @@
+using AdvancedTodoList.Core.Models.TodoLists.Members;
+using AdvancedTodoList.Core.Specifications.Filters;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications.Todo;
+
+///
+/// Represents a specification that defines criteria for filtering to-do list members
+/// and adds includes for users and roles models.
+///
+/// ID of the to-do list items of which will be obtained.
+/// Filter parameters.
+public class TodoListMembersSpecification(string todoListId, TodoListMembersFilter filter) : ISpecification
+{
+ ///
+ /// Gets the ID of the to-do list to filter entities by.
+ ///
+ public string? TodoListId { get; } = todoListId;
+ ///
+ /// Gets the filter parameters.
+ ///
+ public TodoListMembersFilter Filter { get; } = filter;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ public Expression> Criteria =>
+ x => x.TodoListId == TodoListId &&
+ // Filter by roles
+ (Filter.RoleId == null || !Filter.RoleId.Any() || Filter.RoleId.Contains(x.RoleId)) &&
+ // Filter by user ID
+ (Filter.UserId == null || x.UserId == Filter.UserId) &&
+ // Filter by username
+ (Filter.UserName == null || x.User!.UserName!.Contains(Filter.UserName)) &&
+ // Filter by full name
+ (Filter.FullName == null || (x.User!.FirstName + ' ' + x.User!.LastName).Contains(Filter.FullName));
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ ///
+ public List>> Includes =>
+ [
+ x => x.Role,
+ x => x.User
+ ];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ public List IncludeStrings => [];
+}
diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs
new file mode 100644
index 0000000..7d7b71c
--- /dev/null
+++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs
@@ -0,0 +1,45 @@
+using AdvancedTodoList.Core.Models.TodoLists;
+using AdvancedTodoList.Core.Specifications.Filters;
+using System.Linq.Expressions;
+
+namespace AdvancedTodoList.Core.Specifications.Todo;
+
+///
+/// Represents a specification that defines criteria for filtering to-do lists,
+/// with the requirement that the user is a member of those lists.
+///
+/// ID of the user.
+/// Filter parameters.
+public class TodoListsSpecification(string userId, TodoListsFilter filter) : ISpecification
+{
+ ///
+ /// Gets the ID of the user, to get lists where this user is a member.
+ ///
+ public string UserId { get; } = userId;
+ ///
+ /// Gets the filter parameters.
+ ///
+ public TodoListsFilter Filter { get; } = filter;
+
+ ///
+ /// Gets the criteria expression that defines the filtering conditions.
+ ///
+ public Expression> Criteria => x =>
+ // User is a member requirement
+ x.TodoListMembers.Any(m => m.UserId == UserId) &&
+ // Filter by name
+ (Filter.Name == null || x.Name.Contains(Filter.Name));
+
+ ///
+ /// Gets the list of include expressions specifying related entities to be included in the query results.
+ ///
+ public List>> Includes =>
+ [
+ x => x.TodoListMembers
+ ];
+
+ ///
+ /// Gets the list of include strings specifying related entities to be included in the query results.
+ ///
+ public List IncludeStrings => [];
+}
diff --git a/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs
deleted file mode 100644
index 83c5f3f..0000000
--- a/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation.Auth;
-
-public class LogInDtoValidator : AbstractValidator
-{
- public LogInDtoValidator()
- {
- RuleFor(x => x.UserNameOrEmail)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- RuleFor(x => x.Password)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs
deleted file mode 100644
index 834e5dc..0000000
--- a/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation.Auth;
-
-public class LogOutDtoValidator : AbstractValidator
-{
- public LogOutDtoValidator()
- {
- RuleFor(x => x.RefreshToken)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs
deleted file mode 100644
index 0e85bb9..0000000
--- a/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation.Auth;
-
-public class RefreshDtoValidator : AbstractValidator
-{
- public RefreshDtoValidator()
- {
- RuleFor(x => x.AccessToken)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- RuleFor(x => x.RefreshToken)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs
deleted file mode 100644
index 1475026..0000000
--- a/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using FluentValidation;
-using FluentValidation.Validators;
-
-namespace AdvancedTodoList.Core.Validation.Auth;
-
-public class RegisterDtoValidator : AbstractValidator
-{
- public RegisterDtoValidator()
- {
- RuleFor(x => x.Email)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- RuleFor(x => x.Email)
- .EmailAddress(EmailValidationMode.AspNetCoreCompatible)
- .WithErrorCode(ValidationErrorCodes.InvalidEmail);
-
- RuleFor(x => x.UserName)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- RuleFor(x => x.FirstName)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- RuleFor(x => x.LastName)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- RuleFor(x => x.Password)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs b/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs
deleted file mode 100644
index 1a9450a..0000000
--- a/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using AdvancedTodoList.Core.Pagination;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-public class PaginationParametersValidator : AbstractValidator
-{
- public PaginationParametersValidator()
- {
- RuleFor(x => x.Page)
- .GreaterThan(0)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage("Page number must be a positive number.");
-
- RuleFor(x => x.PageSize)
- .GreaterThanOrEqualTo(PaginationParameters.MinPageSize)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}");
-
- RuleFor(x => x.PageSize)
- .LessThanOrEqualTo(PaginationParameters.MaxPageSize)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}");
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs
deleted file mode 100644
index a46c7da..0000000
--- a/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-public class TodoItemCategoryCreateDtoValidator : AbstractValidator
-{
- public TodoItemCategoryCreateDtoValidator()
- {
- // Name is required
- RuleFor(x => x.Name)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- // Name should not be too long
- RuleFor(x => x.Name)
- .MaximumLength(TodoItemCategory.NameMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs
deleted file mode 100644
index 3d3ebe0..0000000
--- a/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-///
-/// Validator class for
-///
-public class TodoItemCreateDtoValidator : AbstractValidator
-{
- public TodoItemCreateDtoValidator()
- {
- // Name is required
- RuleFor(x => x.Name)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- // Name should not be too long
- RuleFor(x => x.Name)
- .MaximumLength(TodoItem.NameMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
-
- // Description is not null
- RuleFor(x => x.Description)
- .NotNull()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- // Description should not be too long
- RuleFor(x => x.Description)
- .MaximumLength(TodoItem.DescriptionMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
-
- // Deadline date should be after the current date
- RuleFor(x => x.DeadlineDate)
- .GreaterThan(DateTime.UtcNow)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange);
-
- // Priority must be in a specific range
- RuleFor(x => x.Priority)
- .GreaterThanOrEqualTo(TodoItem.MinPriority)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}");
- RuleFor(x => x.Priority)
- .LessThanOrEqualTo(TodoItem.MaxPriority)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}");
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs
deleted file mode 100644
index a97368a..0000000
--- a/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-public class TodoItemUpdateStateDtoValidator : AbstractValidator
-{
- public TodoItemUpdateStateDtoValidator()
- {
- RuleFor(x => x.State)
- .Must(s => s >= TodoItemState.Active && s <= TodoItemState.Skipped)
- .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange)
- .WithMessage(x => $"{(int)x.State} is invalid value for {{PropertyName}}");
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs
deleted file mode 100644
index 6b21acd..0000000
--- a/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-public class TodoListCreateDtoValidator : AbstractValidator
-{
- public TodoListCreateDtoValidator()
- {
- // Name is required
- RuleFor(x => x.Name)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- // Name should not be too long
- RuleFor(x => x.Name)
- .MaximumLength(TodoList.NameMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
-
- // Description is not null
- RuleFor(x => x.Description)
- .NotNull()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- // Description should not be too long
- RuleFor(x => x.Description)
- .MaximumLength(TodoList.DescriptionMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs
deleted file mode 100644
index 69bc9e5..0000000
--- a/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.Auth;
-using AdvancedTodoList.Core.Services;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-///
-/// Validator class for
-///
-public class TodoListMemberAddDtoValidator : AbstractValidator
-{
- public TodoListMemberAddDtoValidator(IEntityExistenceChecker existenceChecker)
- {
- RuleFor(x => x.UserId)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
-
- RuleFor(x => x.UserId)
- .MustAsync(async (userId, _) =>
- {
- return await existenceChecker.ExistsAsync(userId);
- })
- .WithErrorCode(ValidationErrorCodes.InvalidForeignKey)
- .WithMessage("User not found.");
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs
deleted file mode 100644
index e71e158..0000000
--- a/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using AdvancedTodoList.Core.Dtos;
-using AdvancedTodoList.Core.Models.TodoLists.Members;
-using FluentValidation;
-
-namespace AdvancedTodoList.Core.Validation;
-
-///
-/// Validator class for
-///
-public class TodoListRoleCreateDtoValidator : AbstractValidator
-{
- public TodoListRoleCreateDtoValidator()
- {
- RuleFor(x => x.Name)
- .NotEmpty()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- RuleFor(x => x.Name)
- .MaximumLength(TodoListRole.NameMaxLength)
- .WithErrorCode(ValidationErrorCodes.PropertyTooLong);
-
- RuleFor(x => x.Permissions)
- .NotNull()
- .WithErrorCode(ValidationErrorCodes.PropertyRequired);
- }
-}
diff --git a/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs b/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs
deleted file mode 100644
index 52e5c05..0000000
--- a/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace AdvancedTodoList.Core.Validation;
-
-///
-/// Class that contains validation error codes.
-///
-public static class ValidationErrorCodes
-{
- ///
- /// Required property is null or empty.
- ///
- public const string PropertyRequired = "100";
- ///
- /// Length of the property exceeds maximum possible value.
- ///
- public const string PropertyTooLong = "200";
- ///
- /// Value of the property is out of range.
- ///
- public const string PropertyOutOfRange = "300";
- ///
- /// Property is invalid foreign key.
- ///
- public const string InvalidForeignKey = "400";
- ///
- /// Property is invalid email.
- ///
- public const string InvalidEmail = "500";
-}
diff --git a/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj b/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj
index e8b71c0..ac79dc0 100644
--- a/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj
+++ b/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj
@@ -11,17 +11,18 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
diff --git a/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs b/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs
index db4ede9..4d125c4 100644
--- a/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs
+++ b/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs
@@ -7,24 +7,24 @@
namespace AdvancedTodoList.Infrastructure.Data;
public class ApplicationDbContext(DbContextOptions options) :
- IdentityDbContext(options)
+ IdentityDbContext(options)
{
- public DbSet TodoLists { get; set; }
- public DbSet TodoItems { get; set; }
- public DbSet TodoItemCategories { get; set; }
- public DbSet InvitationLinks { get; set; }
- public DbSet TodoListsMembers { get; set; }
- public DbSet TodoListRoles { get; set; }
- public DbSet UserRefreshTokens { get; set; }
+ public DbSet TodoLists { get; set; }
+ public DbSet TodoItems { get; set; }
+ public DbSet TodoItemCategories { get; set; }
+ public DbSet InvitationLinks { get; set; }
+ public DbSet TodoListsMembers { get; set; }
+ public DbSet TodoListRoles { get; set; }
+ public DbSet UserRefreshTokens { get; set; }
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- base.OnModelCreating(modelBuilder);
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
- modelBuilder.Entity()
- .ComplexProperty(x => x.Permissions);
- modelBuilder.Entity()
- .HasIndex(x => new { x.UserId, x.TodoListId })
- .IsUnique(true);
- }
+ modelBuilder.Entity()
+ .ComplexProperty(x => x.Permissions);
+ modelBuilder.Entity()
+ .HasIndex(x => new { x.UserId, x.TodoListId })
+ .IsUnique(true);
+ }
}
diff --git a/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs b/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs
new file mode 100644
index 0000000..9f2aae7
--- /dev/null
+++ b/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs
@@ -0,0 +1,23 @@
+using AdvancedTodoList.Core.Specifications;
+using Microsoft.EntityFrameworkCore;
+
+namespace AdvancedTodoList.Infrastructure.Extensions;
+
+public static class QueryableSpecificationExtensions
+{
+ public static IQueryable ApplySpecification(this IQueryable queryable, ISpecification specification)
+ where T : class
+ {
+ // Include all expression-based includes
+ var queryableResultWithIncludes = specification.Includes
+ .Aggregate(queryable, (current, include) => current.Include(include));
+
+ // Include string-based include statements
+ var secondaryResult = specification.IncludeStrings
+ .Aggregate(queryableResultWithIncludes,
+ (current, include) => current.Include(include));
+
+ // Apply criteria and return the result
+ return secondaryResult.Where(specification.Criteria);
+ }
+}
diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs b/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs
index b25b781..49aee93 100644
--- a/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs
+++ b/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs
@@ -7,259 +7,259 @@ namespace AdvancedTodoList.Infrastructure.Migrations;
///
public partial class InitialSetup : Migration
{
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "AspNetRoles",
- columns: table => new
- {
- Id = table.Column(type: "nvarchar(450)", nullable: false),
- Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoles", x => x.Id);
- });
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
- migrationBuilder.CreateTable(
- name: "AspNetUsers",
- columns: table => new
- {
- Id = table.Column(type: "nvarchar(450)", nullable: false),
- UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
- EmailConfirmed = table.Column(type: "bit", nullable: false),
- PasswordHash = table.Column(type: "nvarchar(max)", nullable: true),
- SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true),
- ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true),
- PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true),
- PhoneNumberConfirmed = table.Column(type: "bit", nullable: false),
- TwoFactorEnabled = table.Column(type: "bit", nullable: false),
- LockoutEnd = table.Column(type: "datetimeoffset", nullable: true),
- LockoutEnabled = table.Column(type: "bit", nullable: false),
- AccessFailedCount = table.Column(type: "int", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUsers", x => x.Id);
- });
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(type: "bit", nullable: false),
+ PasswordHash = table.Column(type: "nvarchar(max)", nullable: true),
+ SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true),
+ ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true),
+ PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true),
+ PhoneNumberConfirmed = table.Column(type: "bit", nullable: false),
+ TwoFactorEnabled = table.Column(type: "bit", nullable: false),
+ LockoutEnd = table.Column(type: "datetimeoffset", nullable: true),
+ LockoutEnabled = table.Column(type: "bit", nullable: false),
+ AccessFailedCount = table.Column(type: "int", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
- migrationBuilder.CreateTable(
- name: "TodoLists",
- columns: table => new
- {
- Id = table.Column(type: "nvarchar(450)", nullable: false),
- Name = table.Column(type: "nvarchar(max)", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_TodoLists", x => x.Id);
- });
+ migrationBuilder.CreateTable(
+ name: "TodoLists",
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(max)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TodoLists", x => x.Id);
+ });
- migrationBuilder.CreateTable(
- name: "AspNetRoleClaims",
- columns: table => new
- {
- Id = table.Column(type: "int", nullable: false)
- .Annotation("SqlServer:Identity", "1, 1"),
- RoleId = table.Column