diff --git a/src/Api/ConfigureServices.cs b/src/Api/ConfigureServices.cs index 4c4f747f..0a8f60f0 100644 --- a/src/Api/ConfigureServices.cs +++ b/src/Api/ConfigureServices.cs @@ -14,6 +14,7 @@ public static IServiceCollection AddApiServices(this IServiceCollection services { // Register services services.AddServices(); + services.AddHostedService(); services.AddControllers(opt => opt.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()))); diff --git a/src/Api/Controllers/BorrowsController.cs b/src/Api/Controllers/BorrowsController.cs index 484c3949..0388c57a 100644 --- a/src/Api/Controllers/BorrowsController.cs +++ b/src/Api/Controllers/BorrowsController.cs @@ -1,8 +1,11 @@ +using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Requests.Borrows; using Application.Borrows.Commands; using Application.Borrows.Queries; +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Common.Models.Dtos.Physical; using Application.Identity; using Infrastructure.Identity.Authorization; @@ -10,6 +13,7 @@ namespace Api.Controllers; +[Route("api/v1/documents/[controller]")] public class BorrowsController : ApiControllerBase { private readonly ICurrentUserService _currentUserService; @@ -28,7 +32,8 @@ public BorrowsController(ICurrentUserService currentUserService) [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> BorrowDocument([FromBody] BorrowDocumentRequest request) + public async Task>> BorrowDocument( + [FromBody] BorrowDocumentRequest request) { var borrowerId = _currentUserService.GetCurrentUser().Id; var command = new BorrowDocument.Command() @@ -37,7 +42,7 @@ public async Task>> BorrowDocument([FromBody] Bor DocumentId = request.DocumentId, BorrowFrom = request.BorrowFrom, BorrowTo = request.BorrowTo, - Reason = request.Reason, + BorrowReason = request.Reason, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); @@ -53,7 +58,8 @@ public async Task>> BorrowDocument([FromBody] Bor [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> GetById([FromRoute] Guid borrowId) + public async Task>> GetById( + [FromRoute] Guid borrowId) { var user = _currentUserService.GetCurrentUser(); var command = new GetBorrowRequestById.Query() @@ -65,47 +71,25 @@ public async Task>> GetById([FromRoute] Guid borr return Ok(Result.Succeed(result)); } - [RequiresRole(IdentityData.Roles.Staff)] - [HttpGet("staffs")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>>> GetAllRequestsAsStaffPaginated( - [FromQuery] GetAllBorrowRequestsPaginatedAsStaffQueryParameters queryParameters) - { - var departmentId = _currentUserService.GetCurrentDepartmentForStaff(); - var command = new GetAllBorrowRequestsPaginated.Query() - { - DepartmentId = departmentId, - EmployeeId = queryParameters.EmployeeId, - DocumentId = queryParameters.DocumentId, - Page = queryParameters.Page, - Size = queryParameters.Size, - SortBy = queryParameters.SortBy, - SortOrder = queryParameters.SortOrder, - }; - var result = await Mediator.Send(command); - return Ok(Result>.Succeed(result)); - } - /// - /// Get all borrow requests as admin paginated + /// /// /// /// - [RequiresRole(IdentityData.Roles.Admin)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>>> GetAllRequestsAsAdminPaginated( - [FromQuery] GetAllBorrowRequestsPaginatedAsAdminQueryParameters queryParameters) + public async Task>>> GetAllRequestsPaginated( + [FromQuery] GetAllBorrowRequestsPaginatedQueryParameters queryParameters) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new GetAllBorrowRequestsPaginated.Query() { - DepartmentId = queryParameters.DepartmentId, + CurrentUser = currentUser, + RoomId = queryParameters.RoomId, EmployeeId = queryParameters.EmployeeId, DocumentId = queryParameters.DocumentId, Page = queryParameters.Page, @@ -118,99 +102,28 @@ public async Task>>> GetAllRequests } /// - /// Get all borrow requests as employee paginated - /// - /// - /// - [RequiresRole(IdentityData.Roles.Employee)] - [HttpGet("employees")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>>> GetAllRequestsAsEmployeePaginated( - [FromQuery] GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters queryParameters) - { - var userId = _currentUserService.GetCurrentUser().Id; - var command = new GetAllBorrowRequestsPaginated.Query() - { - EmployeeId = userId, - DocumentId = queryParameters.DocumentId, - Page = queryParameters.Page, - Size = queryParameters.Size, - SortBy = queryParameters.SortBy, - SortOrder = queryParameters.SortOrder, - }; - var result = await Mediator.Send(command); - return Ok(Result>.Succeed(result)); - } - - /// - /// Get all borrow requests for a document paginated - /// - /// - /// - /// - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpGet("documents/{documentId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>>> GetAllRequestsForDocumentPaginated( - [FromRoute] Guid documentId, - [FromQuery] GetAllBorrowRequestsPaginatedForDocumentQueryParameters queryParameters) - { - var command = new GetAllBorrowRequestsPaginated.Query() - { - DocumentId = documentId, - Page = queryParameters.Page, - Size = queryParameters.Size, - SortBy = queryParameters.SortBy, - SortOrder = queryParameters.SortOrder, - Status = queryParameters.Status, - }; - var result = await Mediator.Send(command); - return Ok(Result>.Succeed(result)); - } - - /// - /// Approve a borrow request + /// Approve or Reject a borrow request /// /// Id of the borrow request to be approved + /// /// A BorrowDto of the approved borrow request [RequiresRole(IdentityData.Roles.Staff)] - [HttpPost("approve/{borrowId:guid}")] + [HttpPut("staffs/{borrowId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> ApproveRequest([FromRoute] Guid borrowId) - { - var command = new ApproveBorrowRequest.Command() - { - BorrowId = borrowId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - - /// - /// Reject a borrow request - /// - /// Id of the borrow request to be rejected - /// A BorrowDto of the rejected borrow request - [RequiresRole(IdentityData.Roles.Staff)] - [HttpPost("reject/{borrowId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> RejectRequest([FromRoute] Guid borrowId) + public async Task>> ApproveOrRejectRequest( + [FromRoute] Guid borrowId, + [FromBody] ApproveOrRejectBorrowRequestRequest request) { - var command = new RejectBorrowRequest.Command() + var performingUserId = _currentUserService.GetId(); + var command = new ApproveOrRejectBorrowRequest.Command() { + CurrentUserId = performingUserId, BorrowId = borrowId, + StaffReason = request.StaffReason, + Decision = request.Decision }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); @@ -227,10 +140,13 @@ public async Task>> RejectRequest([FromRoute] Gui [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Checkout([FromRoute] Guid borrowId) + public async Task>> Checkout( + [FromRoute] Guid borrowId) { + var currentStaff = _currentUserService.GetCurrentUser(); var command = new CheckoutDocument.Command() { + CurrentStaff = currentStaff, BorrowId = borrowId, }; var result = await Mediator.Send(command); @@ -248,10 +164,13 @@ public async Task>> Checkout([FromRoute] Guid bor [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Return([FromRoute] Guid documentId) + public async Task>> Return( + [FromRoute] Guid documentId) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new ReturnDocument.Command() { + CurrentUser = currentUser, DocumentId = documentId, }; var result = await Mediator.Send(command); @@ -274,12 +193,14 @@ public async Task>> Update( [FromRoute] Guid borrowId, [FromBody] UpdateBorrowRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new UpdateBorrow.Command() { + CurrentUser = currentUser, BorrowId = borrowId, BorrowFrom = request.BorrowFrom, BorrowTo = request.BorrowTo, - Reason = request.Reason, + BorrowReason = request.Reason, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); @@ -296,13 +217,38 @@ public async Task>> Update( [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Cancel([FromRoute] Guid borrowId) + public async Task>> Cancel( + [FromRoute] Guid borrowId) { + var currentUserId = _currentUserService.GetId(); var command = new CancelBorrowRequest.Command() { + CurrentUserId = currentUserId, BorrowId = borrowId, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } + + /// + /// Get all logs related to requests. + /// + /// Query parameters + /// A list of RequestLogsDtos. + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task>>> GetAllRequestLogs( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllRequestLogsPaginated.Query() + { + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } } \ No newline at end of file diff --git a/src/Api/Controllers/DepartmentsController.cs b/src/Api/Controllers/DepartmentsController.cs index bf01c28a..1292c8e1 100644 --- a/src/Api/Controllers/DepartmentsController.cs +++ b/src/Api/Controllers/DepartmentsController.cs @@ -1,9 +1,12 @@ using Api.Controllers.Payload.Requests.Departments; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.Physical; using Application.Departments.Commands; using Application.Departments.Queries; using Application.Identity; +using Application.Rooms.Queries; using Application.Users.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,29 +15,67 @@ namespace Api.Controllers; public class DepartmentsController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public DepartmentsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// - /// Get back a department based on its id + /// Get back a room based on its department id /// /// id of the department to be retrieved /// A DepartmentDto of the retrieved department + [RequiresRole( + IdentityData.Roles.Admin, + IdentityData.Roles.Staff, + IdentityData.Roles.Employee)] [HttpGet("{departmentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetById([FromRoute] Guid departmentId) + public async Task>> GetById( + [FromRoute] Guid departmentId) { + var role = _currentUserService.GetRole(); + var userDepartmentId = _currentUserService.GetDepartmentId(); var query = new GetDepartmentById.Query() { - DepartmentId = departmentId + UserRole = role, + UserDepartmentId = userDepartmentId, + DepartmentId = departmentId, }; var result = await Mediator.Send(query); return Ok(Result.Succeed(result)); } + /// + /// Get back a department based on its id + /// + /// id of the department to be retrieved + /// A DepartmentDto of the retrieved department + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("{departmentId:guid}/rooms")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetRoomByDepartmentId( + [FromRoute] Guid departmentId) + { + var query = new GetRoomByDepartmentId.Query() + { + DepartmentId = departmentId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + /// /// Get all documents /// /// A list of DocumentDto + [RequiresRole(IdentityData.Roles.Admin)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -54,7 +95,8 @@ public async Task>>> GetAll() [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Add([FromBody] AddDepartmentRequest request) + public async Task>> Add( + [FromBody] AddDepartmentRequest request) { var command = new AddDepartment.Command() { @@ -64,28 +106,6 @@ public async Task>> Add([FromBody] AddDepartm return Ok(Result.Succeed(result)); } - /// - /// Update a department - /// - /// Id of the department to be updated - /// Update department details - /// A DepartmentDto of the updated department - [RequiresRole(IdentityData.Roles.Admin)] - [HttpPut("{departmentId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> Update([FromRoute] Guid departmentId, [FromBody] UpdateDepartmentRequest request) - { - var command = new UpdateDepartment.Command() - { - DepartmentId = departmentId, - Name = request.Name - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - /// /// Delete a department /// diff --git a/src/Api/Controllers/DocumentsController.cs b/src/Api/Controllers/DocumentsController.cs index 2a2f369f..2d500422 100644 --- a/src/Api/Controllers/DocumentsController.cs +++ b/src/Api/Controllers/DocumentsController.cs @@ -1,5 +1,9 @@ +using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Requests.Documents; +using Application.Common.Extensions; +using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Common.Models.Dtos.Physical; using Application.Documents.Commands; using Application.Documents.Queries; @@ -11,19 +15,30 @@ namespace Api.Controllers; public class DocumentsController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public DocumentsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a document by id /// /// Id of the document to be retrieved /// A DocumentDto of the retrieved document + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Staff)] [HttpGet("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetById([FromRoute] Guid documentId) + public async Task>> GetById( + [FromRoute] Guid documentId) { + var currentUser = _currentUserService.GetCurrentUser(); var query = new GetDocumentById.Query() { + CurrentUser = currentUser, DocumentId = documentId, }; var result = await Mediator.Send(query); @@ -35,6 +50,7 @@ public async Task>> GetById([FromRoute] Guid do /// /// Get all documents query parameters /// A paginated list of DocumentDto + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -43,8 +59,17 @@ public async Task>> GetById([FromRoute] Guid do public async Task>>> GetAllPaginated( [FromQuery] GetAllDocumentsPaginatedQueryParameters queryParameters) { + var currentUser = _currentUserService.GetCurrentUser(); + Guid? currentStaffRoomId = null; + if (currentUser.Role.IsStaff()) + { + currentStaffRoomId = _currentUserService.GetCurrentRoomForStaff(); + } var query = new GetAllDocumentsPaginated.Query() { + CurrentUser = currentUser, + CurrentStaffRoomId = currentStaffRoomId, + UserId = queryParameters.UserId, RoomId = queryParameters.RoomId, LockerId = queryParameters.LockerId, FolderId = queryParameters.FolderId, @@ -53,11 +78,46 @@ public async Task>>> GetAllPagina Size = queryParameters.Size, SortBy = queryParameters.SortBy, SortOrder = queryParameters.SortOrder, + IsPrivate = queryParameters.IsPrivate, + DocumentStatus = queryParameters.DocumentStatus, + Role = queryParameters.UserRole, }; var result = await Mediator.Send(query); return Ok(Result>.Succeed(result)); } - + + /// + /// Get all documents paginated + /// + /// Get all documents query parameters + /// A paginated list of DocumentDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("employees")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllForEmployeePaginated( + [FromQuery] GetAllDocumentsForEmployeePaginatedQueryParameters queryParameters) + { + var currentUserId = _currentUserService.GetId(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetAllDocumentsForEmployeePaginated.Query() + { + CurrentUserId = currentUserId, + CurrentUserDepartmentId = currentUserDepartmentId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + DocumentStatus = queryParameters.DocumentStatus, + IsPrivate = queryParameters.IsPrivate, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + /// /// Get all document types /// @@ -77,17 +137,26 @@ public async Task>>> GetAllDocumentTypes /// /// Import document details /// A DocumentDto of the imported document - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [RequiresRole(IdentityData.Roles.Staff)] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Import([FromBody] ImportDocumentRequest request) + public async Task>> Import( + [FromBody] ImportDocumentRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + var currentStaffRoomId = _currentUserService.GetCurrentRoomForStaff(); + if (currentUser.Department is null) + { + return Forbid(); + } var command = new ImportDocument.Command() { + CurrentUser = currentUser, + CurrentStaffRoomId = currentStaffRoomId, Title = request.Title, Description = request.Description, DocumentType = request.DocumentType, @@ -104,20 +173,29 @@ public async Task>> Import([FromBody] ImportDoc /// Id of the document to be updated /// Update document details /// A DocumentDto of the updated document - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpPut("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Update([FromRoute] Guid documentId, [FromBody] UpdateDocumentRequest request) + public async Task>> Update( + [FromRoute] Guid documentId, + [FromBody] UpdateDocumentRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser.Department is null) + { + return Forbid(); + } var query = new UpdateDocument.Command() { + CurrentUser = currentUser, DocumentId = documentId, Title = request.Title, Description = request.Description, - DocumentType = request.DocumentType + DocumentType = request.DocumentType, + IsPrivate = request.IsPrivate, }; var result = await Mediator.Send(query); return Ok(Result.Succeed(result)); @@ -127,18 +205,101 @@ public async Task>> Update([FromRoute] Guid doc /// Delete a document /// /// Id of the document to be deleted - /// A DocumentDto of the deleted document + /// A DocumentDto of the deleted document + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpDelete("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> Delete([FromRoute] Guid documentId) + public async Task>> Delete( + [FromRoute] Guid documentId) { + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser.Role.IsStaff() + && currentUser.Department is null) + { + return Forbid(); + } var query = new DeleteDocument.Command() { + CurrentUser = currentUser, DocumentId = documentId, }; var result = await Mediator.Send(query); return Ok(Result.Succeed(result)); } + + /// + /// Get permissions for an employee of a specific document + /// + /// Id of the document to be getting permissions from + /// A DocumentDto of the rejected document + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("{documentId:guid}/permissions")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetPermissions( + [FromRoute] Guid documentId) + { + var performingUser = _currentUserService.GetCurrentUser(); + var query = new GetPermissions.Query() + { + CurrentUser = performingUser, + DocumentId = documentId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Share permissions for an employee of a specific document + /// + /// Id of the document + /// + /// A DocumentDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost("{documentId:guid}/permissions")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> SharePermissions( + [FromRoute] Guid documentId, + [FromBody] SharePermissionsRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new ShareDocument.Command() + { + CurrentUser = currentUser, + DocumentId = documentId, + UserId = request.UserId, + CanRead = request.CanRead, + CanBorrow = request.CanBorrow, + ExpiryDate = request.ExpiryDate, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get all log of document + /// + /// + /// Paginated list of DocumentLogDto + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>>> GetAllLogsPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllDocumentLogsPaginated.Query() + { + DocumentId = queryParameters.ObjectId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } } \ No newline at end of file diff --git a/src/Api/Controllers/FoldersController.cs b/src/Api/Controllers/FoldersController.cs index a08b36cc..153367ab 100644 --- a/src/Api/Controllers/FoldersController.cs +++ b/src/Api/Controllers/FoldersController.cs @@ -1,5 +1,9 @@ +using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Requests.Folders; +using Application.Common.Extensions; +using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Common.Models.Dtos.Physical; using Application.Folders.Commands; using Application.Folders.Queries; @@ -11,6 +15,13 @@ namespace Api.Controllers; public class FoldersController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public FoldersController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a folder by id /// @@ -23,8 +34,12 @@ public class FoldersController : ApiControllerBase [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetById([FromRoute] Guid folderId) { + var currentUserRole = _currentUserService.GetRole(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); var query = new GetFolderById.Query() { + CurrentUserRole = currentUserRole, + CurrentStaffRoomId = staffRoomId, FolderId = folderId, }; var result = await Mediator.Send(query); @@ -43,8 +58,12 @@ public async Task>> GetById([FromRoute] Guid fold public async Task>>> GetAllPaginated( [FromQuery] GetAllFoldersPaginatedQueryParameters queryParameters) { + var currentUserRole = _currentUserService.GetRole(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); var query = new GetAllFoldersPaginated.Query() { + CurrentUserRole = currentUserRole, + CurrentStaffRoomId = staffRoomId, RoomId = queryParameters.RoomId, LockerId = queryParameters.LockerId, SearchTerm = queryParameters.SearchTerm, @@ -62,7 +81,7 @@ public async Task>>> GetAllPaginate /// /// Add folder details /// A FolderDto of the added folder - [RequiresRole(IdentityData.Roles.Admin)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -71,8 +90,12 @@ public async Task>>> GetAllPaginate [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task>> AddFolder([FromBody] AddFolderRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); var command = new AddFolder.Command() { + CurrentUser = currentUser, + CurrentStaffRoomId = staffRoomId, Name = request.Name, Description = request.Description, Capacity = request.Capacity, @@ -87,7 +110,7 @@ public async Task>> AddFolder([FromBody] AddFolde /// /// Id of the folder to be removed /// A FolderDto of the removed folder - [RequiresRole(IdentityData.Roles.Admin)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpDelete("{folderId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -96,58 +119,22 @@ public async Task>> AddFolder([FromBody] AddFolde [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task>> RemoveFolder([FromRoute] Guid folderId) { - var command = new RemoveFolder.Command() + var currentUser = _currentUserService.GetCurrentUser(); + Guid? staffRoomId = null; + if (currentUser.Role.IsStaff()) { - FolderId = folderId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - - /// - /// Enable a folder - /// - /// Id of the folder to be enabled - /// A FolderDto of the enabled folder - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpPut("enable/{folderId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> EnableFolder([FromRoute] Guid folderId) - { - var command = new EnableFolder.Command() + staffRoomId = _currentUserService.GetCurrentRoomForStaff(); + } + var command = new RemoveFolder.Command() { + CurrentUser = currentUser, + CurrentStaffRoomId = staffRoomId, FolderId = folderId, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } - /// - /// Disable a folder - /// - /// Id of the disabled folder - /// A FolderDto of the disabled folder - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpPut("disable/{folderId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> DisableFolder([FromRoute] Guid folderId) - { - var command = new DisableFolder.Command() - { - FolderId = folderId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - /// /// Update a folder /// @@ -159,10 +146,16 @@ public async Task>> DisableFolder([FromRoute] Gui [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Update([FromRoute] Guid folderId, [FromBody] UpdateFolderRequest request) + public async Task>> Update( + [FromRoute] Guid folderId, + [FromBody] UpdateFolderRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); var command = new UpdateFolder.Command() { + CurrentUser = currentUser, + CurrentStaffRoomId = staffRoomId, FolderId = folderId, Name = request.Name, Description = request.Description, @@ -171,4 +164,27 @@ public async Task>> Update([FromRoute] Guid folde var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } + + /// + /// + /// + /// + /// + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task>>> GetAllLogsPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllFolderLogsPaginated.Query() + { + FolderId = queryParameters.ObjectId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } } \ No newline at end of file diff --git a/src/Api/Controllers/ImportRequestsController.cs b/src/Api/Controllers/ImportRequestsController.cs new file mode 100644 index 00000000..dc7f0bfc --- /dev/null +++ b/src/Api/Controllers/ImportRequestsController.cs @@ -0,0 +1,187 @@ +using Api.Controllers.Payload.Requests.Documents; +using Api.Controllers.Payload.Requests.ImportRequests; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.ImportDocument; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using Application.ImportRequests.Commands; +using Application.ImportRequests.Queries; +using Domain.Enums; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +[Route("api/v1/documents/[controller]")] +public class ImportRequestsController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + + public ImportRequestsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Get an import request by id. + /// + /// Id of the request> + /// An ImportRequestDto of the request + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpGet("{importRequestId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetImportRequestById( + [FromRoute] Guid importRequestId) + { + var currentUserId = _currentUserService.GetId(); + var currentUserRole = _currentUserService.GetRole(); + var currentStaffRoomId = _currentUserService.GetCurrentRoomForStaff(); + var query = new GetImportRequestById.Query() + { + CurrentUserId = currentUserId, + CurrentUserRole = currentUserRole, + CurrentStaffRoomId = currentStaffRoomId, + RequestId = importRequestId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// + /// + /// + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpGet] + public async Task>>> GetAllImportRequestsPaginated( + [FromQuery] GetAllImportRequestsPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetAllImportRequestsPaginated.Query() + { + CurrentUser = currentUser, + SearchTerm = queryParameters.SearchTerm, + RoomId = queryParameters.RoomId, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Request to import a document + /// + /// Import document request details + /// A DocumentDto of the imported document + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> RequestImport( + [FromBody] RequestImportDocumentRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new RequestImportDocument.Command() + { + Title = request.Title, + Description = request.Description, + DocumentType = request.DocumentType, + ImportReason = request.ImportReason, + IsPrivate = request.IsPrivate, + Issuer = currentUser, + RoomId = request.RoomId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Approve or reject a document request + /// + /// Id of the document to be approved + /// + /// A DocumentDto of the approved document + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPut("{importRequestId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> ApproveOrReject( + [FromRoute] Guid importRequestId, + [FromBody] ApproveOrRejectImportRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new ApproveOrRejectDocument.Command() + { + CurrentUser = currentUser, + ImportRequestId = importRequestId, + StaffReason = request.StaffReason, + Decision = request.Decision, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Assign a document to a folder + /// + /// Id of the import request to be rejected or approved + /// + /// A DocumentDto of the rejected document + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPut("assign/{importRequestId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Assign( + [FromRoute] Guid importRequestId, + [FromBody] AssignDocumentToFolderRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); + var query = new AssignDocument.Command() + { + CurrentUser = currentUser, + StaffRoomId = staffRoomId, + ImportRequestId = importRequestId, + FolderId = request.FolderId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Checkin a document + /// + /// + /// A DocumentDto of the imported document + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPut("checkin/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Checkin( + [FromRoute] Guid documentId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new CheckinDocument.Command() + { + CurrentUser = currentUser, + DocumentId = documentId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } +} \ No newline at end of file diff --git a/src/Api/Controllers/LockersController.cs b/src/Api/Controllers/LockersController.cs index 2445df95..fb87d0ee 100644 --- a/src/Api/Controllers/LockersController.cs +++ b/src/Api/Controllers/LockersController.cs @@ -1,5 +1,8 @@ -using Api.Controllers.Payload.Requests.Lockers; +using Api.Controllers.Payload.Requests; +using Api.Controllers.Payload.Requests.Lockers; +using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Common.Models.Dtos.Physical; using Application.Identity; using Application.Lockers.Commands; @@ -11,19 +14,32 @@ namespace Api.Controllers; public class LockersController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public LockersController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a locker by id /// /// Id of the locker to be retrieved /// A LockerDto of the retrieved locker + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpGet("{lockerId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetById([FromRoute] Guid lockerId) + public async Task>> GetById( + [FromRoute] Guid lockerId) { + var currentUserRole = _currentUserService.GetRole(); + var staffRoomId = _currentUserService.GetCurrentRoomForStaff(); var query = new GetLockerById.Query() { + CurrentUserRole = currentUserRole, + CurrentStaffRoomId = staffRoomId, LockerId = lockerId, }; var result = await Mediator.Send(query); @@ -35,14 +51,19 @@ public async Task>> GetById([FromRoute] Guid lock /// /// Get all lockers paginated query parameters /// A paginated list of LockerDto + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task>>> GetAllPaginated( [FromQuery] GetAllLockersPaginatedQueryParameters queryParameters) { + var currentUserRole = _currentUserService.GetRole(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); var query = new GetAllLockersPaginated.Query() { + CurrentUserRole = currentUserRole, + CurrentUserDepartmentId = currentUserDepartmentId, RoomId = queryParameters.RoomId, SearchTerm = queryParameters.SearchTerm, Page = queryParameters.Page, @@ -66,10 +87,13 @@ public async Task>>> GetAllPaginate [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Add([FromBody] AddLockerRequest request) + public async Task>> Add( + [FromBody] AddLockerRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new AddLocker.Command() { + CurrentUser = currentUser, Name = request.Name, Description = request.Description, Capacity = request.Capacity, @@ -84,6 +108,7 @@ public async Task>> Add([FromBody] AddLockerReque /// /// Id of the locker to be removed /// A LockerDto of the removed locker + [RequiresRole(IdentityData.Roles.Admin)] [HttpDelete("{lockerId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -91,8 +116,10 @@ public async Task>> Add([FromBody] AddLockerReque [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task>> Remove([FromRoute] Guid lockerId) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new RemoveLocker.Command() { + CurrentUser = currentUser, LockerId = lockerId, }; var result = await Mediator.Send(command); @@ -100,70 +127,58 @@ public async Task>> Remove([FromRoute] Guid locke } /// - /// Enable a locker + /// Update a locker /// - /// Id of the locker to be enabled - /// A LockerDto of the enabled locker + /// Id of the locker to be updated + /// Update locker details + /// A LockerDto of the updated locker [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpPut("enable/{lockerId:guid}")] + [HttpPut("{lockerId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Enable([FromRoute] Guid lockerId) + public async Task>> Update( + [FromRoute] Guid lockerId, + [FromBody] UpdateLockerRequest request) { - var command = new EnableLocker.Command() + var currentUser = _currentUserService.GetCurrentUser(); + var command = new UpdateLocker.Command() { + CurrentUser = currentUser, LockerId = lockerId, + Name = request.Name, + Description = request.Description, + Capacity = request.Capacity, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } /// - /// Disable a locker - /// - /// Id of the locker to be disabled - /// A LockerDto of the disabled locker - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpPut("disable/{lockerId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Disable([FromRoute] Guid lockerId) - { - var command = new DisableLocker.Command() - { - LockerId = lockerId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - - /// - /// Update a locker + /// Get all logs related to locker /// - /// Id of the locker to be updated - /// Update locker details - /// A LockerDto of the updated locker - [HttpPut("{lockerId:guid}")] + /// Query parameters + /// A list of LockerLogsDtos + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Update([FromRoute] Guid lockerId, [FromBody] UpdateLockerRequest request) + public async Task>>> GetAllLogsPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) { - var command = new UpdateLocker.Command() + var currentUserRole = _currentUserService.GetRole(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetAllLockerLogsPaginated.Query() { - LockerId = lockerId, - Name = request.Name, - Description = request.Description, - Capacity = request.Capacity, + CurrentUserRole = currentUserRole, + CurrentUserDepartmentId = currentUserDepartmentId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + LockerId = queryParameters.ObjectId }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); } } diff --git a/src/Api/Controllers/Payload/Requests/Borrows/ApproveOrRejectBorrowRequestRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/ApproveOrRejectBorrowRequestRequest.cs new file mode 100644 index 00000000..7cd10e4c --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/ApproveOrRejectBorrowRequestRequest.cs @@ -0,0 +1,7 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class ApproveOrRejectBorrowRequestRequest +{ + public string StaffReason { get; set; } + public string Decision { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs similarity index 59% rename from src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs rename to src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs index 8ea8eca1..aa844929 100644 --- a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs @@ -3,12 +3,12 @@ namespace Api.Controllers.Payload.Requests.Borrows; /// /// Query parameters for getting all borrow requests with pagination as admin /// -public class GetAllBorrowRequestsPaginatedAsAdminQueryParameters : PaginatedQueryParameters +public class GetAllBorrowRequestsPaginatedQueryParameters : PaginatedQueryParameters { /// - /// Id of the department to get borrow requests in + /// Id of the room to get borrow requests in /// - public Guid? DepartmentId { get; set; } + public Guid? RoomId { get; set; } public Guid? DocumentId { get; set; } public Guid? EmployeeId { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/RejectRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/RejectRequest.cs new file mode 100644 index 00000000..dcbfbfce --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/RejectRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class RejectRequest +{ + public string Reason { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/ApproveOrRejectImportRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/ApproveOrRejectImportRequest.cs new file mode 100644 index 00000000..5281b1e5 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/ApproveOrRejectImportRequest.cs @@ -0,0 +1,7 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class ApproveOrRejectImportRequest +{ + public string Decision { get; set; } = null!; + public string StaffReason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs new file mode 100644 index 00000000..bcca52f5 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class AssignDocumentToFolderRequest +{ + public Guid FolderId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForEmployeePaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForEmployeePaginatedQueryParameters.cs new file mode 100644 index 00000000..9b6be2b0 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForEmployeePaginatedQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllDocumentsForEmployeePaginatedQueryParameters : PaginatedQueryParameters +{ + public Guid? UserId { get; set; } + public string? SearchTerm { get; set; } + public string? DocumentStatus { get; set; } + public bool IsPrivate { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs new file mode 100644 index 00000000..375a6597 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs @@ -0,0 +1,17 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllDocumentsForStaffPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the locker to find documents in + /// + public Guid? LockerId { get; set; } + /// + /// Id of the folder to find documents in + /// + public Guid? FolderId { get; set; } + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs index 7b97fc8f..94cca9b8 100644 --- a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs @@ -5,6 +5,7 @@ namespace Api.Controllers.Payload.Requests.Documents; /// public class GetAllDocumentsPaginatedQueryParameters : PaginatedQueryParameters { + public Guid? UserId { get; set; } /// /// Id of the room to find documents in /// @@ -21,4 +22,7 @@ public class GetAllDocumentsPaginatedQueryParameters : PaginatedQueryParameters /// Search term /// public string? SearchTerm { get; set; } + public string? DocumentStatus { get; set; } + public string? UserRole { get; set; } + public bool? IsPrivate { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs new file mode 100644 index 00000000..ed060421 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllIssuedPaginatedQueryParameters : PaginatedQueryParameters +{ + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetDocumentsOfUserPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetDocumentsOfUserPaginatedQueryParameters.cs new file mode 100644 index 00000000..a09e0eee --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetDocumentsOfUserPaginatedQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetDocumentsOfUserPaginatedQueryParameters : PaginatedQueryParameters +{ + +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetSelfDocumentsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetSelfDocumentsPaginatedQueryParameters.cs new file mode 100644 index 00000000..3f7d6472 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetSelfDocumentsPaginatedQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +/// +/// Query parameters for getting all documents that belong to an employee +/// +public class GetSelfDocumentsPaginatedQueryParameters : PaginatedQueryParameters +{ + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs new file mode 100644 index 00000000..1e9c4b80 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class RejectImportRequest +{ + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs new file mode 100644 index 00000000..c673896d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +/// +/// Request details to import a document +/// +public class RequestImportDocumentRequest +{ + /// + /// Title of the document to be imported + /// + public string Title { get; set; } = null!; + /// + /// Description of the document to be imported + /// + public string? Description { get; set; } + /// + /// Document type of the document to be imported + /// + public string DocumentType { get; set; } = null!; + public string ImportReason { get; set; } = null!; + + public Guid RoomId { get; set; } + public bool IsPrivate { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/SharePermissionsRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/SharePermissionsRequest.cs new file mode 100644 index 00000000..6a26ec1b --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/SharePermissionsRequest.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class SharePermissionsRequest +{ + public Guid UserId { get; set; } + public bool CanRead { get; set; } + public bool CanBorrow { get; set; } + public DateTime ExpiryDate { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/UpdateDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/UpdateDocumentRequest.cs index a31cec88..97626d8a 100644 --- a/src/Api/Controllers/Payload/Requests/Documents/UpdateDocumentRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Documents/UpdateDocumentRequest.cs @@ -17,4 +17,5 @@ public class UpdateDocumentRequest /// New document type of the document to be updated /// public string DocumentType { get; set; } = null!; + public bool IsPrivate { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/GetAllLogsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/GetAllLogsPaginatedQueryParameters.cs new file mode 100644 index 00000000..59019d0d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/GetAllLogsPaginatedQueryParameters.cs @@ -0,0 +1,25 @@ +namespace Api.Controllers.Payload.Requests; + +/// +/// Get all logs paginated +/// +public class GetAllLogsPaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Size number + /// + public int? Size { get; set; } + + /// + /// User Id + /// + public Guid? ObjectId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/ImportRequests/GetAllImportRequestsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/ImportRequests/GetAllImportRequestsPaginatedQueryParameters.cs new file mode 100644 index 00000000..c66c1956 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/ImportRequests/GetAllImportRequestsPaginatedQueryParameters.cs @@ -0,0 +1,10 @@ +namespace Api.Controllers.Payload.Requests.ImportRequests; + +/// +/// +/// +public class GetAllImportRequestsPaginatedQueryParameters : PaginatedQueryParameters +{ + public string? SearchTerm { get; set; } + public Guid? RoomId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Lockers/AddLockerRequest.cs b/src/Api/Controllers/Payload/Requests/Lockers/AddLockerRequest.cs index 67988e72..21374f9a 100644 --- a/src/Api/Controllers/Payload/Requests/Lockers/AddLockerRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Lockers/AddLockerRequest.cs @@ -8,17 +8,17 @@ public class AddLockerRequest /// /// Name of the locker to be updated /// - public string Name { get; init; } = null!; + public string Name { get; set; } = null!; /// /// Description of the locker to be updated /// - public string? Description { get; init; } + public string? Description { get; set; } /// /// Id of the room that this locker will be in /// - public Guid RoomId { get; init; } + public Guid RoomId { get; set; } /// /// Number of folders this locker can hold /// - public int Capacity { get; init; } + public int Capacity { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs index 390ae4be..3bf5e63a 100644 --- a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs @@ -9,4 +9,5 @@ public class GetAllRoomsPaginatedQueryParameters : PaginatedQueryParameters /// Search term /// public string? SearchTerm { get; set; } + public Guid? DepartmentId { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Rooms/UpdateRoomRequest.cs b/src/Api/Controllers/Payload/Requests/Rooms/UpdateRoomRequest.cs index 40d4965c..d277d097 100644 --- a/src/Api/Controllers/Payload/Requests/Rooms/UpdateRoomRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Rooms/UpdateRoomRequest.cs @@ -17,4 +17,8 @@ public class UpdateRoomRequest /// New capacity of the room to be updated /// public int Capacity { get; set; } + /// + /// Room availability + /// + public bool IsAvailable { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Staffs/AddStaffRequest.cs b/src/Api/Controllers/Payload/Requests/Staffs/AddStaffRequest.cs index 47ebbc06..6e33caa6 100644 --- a/src/Api/Controllers/Payload/Requests/Staffs/AddStaffRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Staffs/AddStaffRequest.cs @@ -8,7 +8,7 @@ public class AddStaffRequest /// /// User id of the new staff /// - public Guid UserId { get; init; } + public Guid StaffId { get; init; } /// /// Id of the room this staff will be in /// diff --git a/src/Api/Controllers/Payload/Requests/Users/GetAllEmployeesPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Users/GetAllEmployeesPaginatedQueryParameters.cs new file mode 100644 index 00000000..171bce79 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/GetAllEmployeesPaginatedQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Users; + +public class GetAllEmployeesPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs index 47db40c8..738a1017 100644 --- a/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs @@ -8,7 +8,8 @@ public class GetAllUsersPaginatedQueryParameters : PaginatedQueryParameters /// /// Id of the department to find users in /// - public Guid? DepartmentId { get; set; } + public Guid[]? DepartmentIds { get; set; } + public string? Role { get; set; } /// /// Search term /// diff --git a/src/Api/Controllers/Payload/Requests/Users/UpdateSelfRequest.cs b/src/Api/Controllers/Payload/Requests/Users/UpdateSelfRequest.cs new file mode 100644 index 00000000..7b12e5bf --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/UpdateSelfRequest.cs @@ -0,0 +1,16 @@ +namespace Api.Controllers.Payload.Requests.Users; + +/// +/// Request details to update that user +/// +public class UpdateSelfRequest +{ + /// + /// New first name of the user to be updated + /// + public string? FirstName { get; set; } + /// + /// New last name of the user to be updated + /// + public string? LastName { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Users/UpdateUserRequest.cs b/src/Api/Controllers/Payload/Requests/Users/UpdateUserRequest.cs index c30e9871..959567d4 100644 --- a/src/Api/Controllers/Payload/Requests/Users/UpdateUserRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Users/UpdateUserRequest.cs @@ -17,4 +17,6 @@ public class UpdateUserRequest /// New position of the user to be updated /// public string? Position { get; set; } + public string Role { get; set; } = null!; + public bool IsActive { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/RoomsController.cs b/src/Api/Controllers/RoomsController.cs index fa5198ee..1f1dadca 100644 --- a/src/Api/Controllers/RoomsController.cs +++ b/src/Api/Controllers/RoomsController.cs @@ -1,10 +1,14 @@ +using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Requests.Lockers; using Api.Controllers.Payload.Requests.Rooms; +using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Common.Models.Dtos.Physical; using Application.Identity; using Application.Rooms.Commands; using Application.Rooms.Queries; +using Application.Staffs.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,19 +16,32 @@ namespace Api.Controllers; public class RoomsController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public RoomsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a room by id /// /// Id of the room to be retrieved /// A RoomDto of the retrieved room + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpGet("{roomId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetById([FromRoute] Guid roomId) + public async Task>> GetById( + [FromRoute] Guid roomId) { + var currentUserRole = _currentUserService.GetRole(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); var query = new GetRoomById.Query() { + CurrentUserRole = currentUserRole, + CurrentUserDepartmentId = currentUserDepartmentId, RoomId = roomId, }; var result = await Mediator.Send(query); @@ -36,15 +53,18 @@ public async Task>> GetById([FromRoute] Guid roomId /// /// Get all rooms paginated details /// A paginated list of rooms - [RequiresRole(IdentityData.Roles.Admin)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task>>> GetAllPaginated( [FromQuery] GetAllRoomsPaginatedQueryParameters queryParameters) { + var currentUser = _currentUserService.GetCurrentUser(); var query = new GetAllRoomsPaginated.Query() { + CurrentUser = currentUser, + DepartmentId = queryParameters.DepartmentId, SearchTerm = queryParameters.SearchTerm, Page = queryParameters.Page, Size = queryParameters.Size, @@ -54,14 +74,15 @@ public async Task>>> GetAllPaginated( var result = await Mediator.Send(query); return Ok(Result>.Succeed(result)); } - + /// /// Get empty containers in a room /// + /// /// Get empty containers paginated details /// A paginated list of EmptyLockerDto [RequiresRole(IdentityData.Roles.Staff)] - [HttpPost("empty-containers/{roomId:guid}")] + [HttpPost("{roomId:guid}/empty-containers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -79,6 +100,29 @@ public async Task>> GetEmptyContainer return Ok(Result>.Succeed(result)); } + /// + /// Get a staff by room + /// + /// Id of the room to retrieve staff + /// A StaffDto of the retrieved staff + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpGet("{roomId:guid}/staffs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetStaffByRoom( + [FromRoute] Guid roomId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetStaffByRoomId.Query() + { + CurrentUser = currentUser, + RoomId = roomId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + /// /// Add a room /// @@ -91,10 +135,13 @@ public async Task>> GetEmptyContainer [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> AddRoom([FromBody] AddRoomRequest request) + public async Task>> AddRoom( + [FromBody] AddRoomRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new AddRoom.Command() { + CurrentUser = currentUser, Name = request.Name, Description = request.Description, Capacity = request.Capacity, @@ -115,51 +162,13 @@ public async Task>> AddRoom([FromBody] AddRoomReque [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> RemoveRoom([FromRoute] Guid roomId) + public async Task>> RemoveRoom( + [FromRoute] Guid roomId) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new RemoveRoom.Command() { - RoomId = roomId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - - /// - /// Enable a room - /// - /// Id of the room to be enabled - /// A RoomDto of the enabled room - [HttpPut("enable/{roomId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> EnableRoom([FromRoute] Guid roomId) - { - var command = new EnableRoom.Command() - { - RoomId = roomId, - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - - /// - /// Disable a room - /// - /// Id of the room to be disabled - /// A RoomDto of the disabled room - [RequiresRole(IdentityData.Roles.Admin)] - [HttpPut("disable/{roomId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> DisableRoom([FromRoute] Guid roomId) - { - var command = new DisableRoom.Command() - { + CurrentUser = currentUser, RoomId = roomId, }; var result = await Mediator.Send(command); @@ -172,21 +181,49 @@ public async Task>> DisableRoom([FromRoute] Guid ro /// Id of the room to be updated /// Update room details /// A RoomDto of the updated room + [RequiresRole(IdentityData.Roles.Admin)] [HttpPut("{roomId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Update([FromRoute] Guid roomId, [FromBody] UpdateRoomRequest request) + public async Task>> Update( + [FromRoute] Guid roomId, + [FromBody] UpdateRoomRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new UpdateRoom.Command() { + CurrentUser = currentUser, RoomId = roomId, Name = request.Name, Description = request.Description, Capacity = request.Capacity, + IsAvailable = request.IsAvailable, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } + + /// + /// Get all room logs paginated + /// + /// + /// A paginated list of RoomLogDto + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>>> GetAllLogsPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllRoomLogsPaginated.Query() + { + RoomId = queryParameters.ObjectId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } } \ No newline at end of file diff --git a/src/Api/Controllers/StaffsController.cs b/src/Api/Controllers/StaffsController.cs index ecd2b9c8..12452352 100644 --- a/src/Api/Controllers/StaffsController.cs +++ b/src/Api/Controllers/StaffsController.cs @@ -1,7 +1,9 @@ using Api.Controllers.Payload.Requests.Staffs; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; using Application.Identity; +using Application.Rooms.Queries; using Application.Staffs.Commands; using Application.Staffs.Queries; using Infrastructure.Identity.Authorization; @@ -11,42 +13,53 @@ namespace Api.Controllers; public class StaffsController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public StaffsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a staff by id /// /// Id of the staff to be retrieved /// A StaffDto of the retrieved staff + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpGet("{staffId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetById([FromRoute] Guid staffId) + public async Task>> GetById( + [FromRoute] Guid staffId) { var query = new GetStaffById.Query() { - StaffId = staffId + StaffId = staffId, }; var result = await Mediator.Send(query); return Ok(Result.Succeed(result)); } /// - /// Get a staff by room + /// Get room by staff id /// - /// Id of the room to retrieve staff - /// A StaffDto of the retrieved staff - [HttpGet("get-by-room/{roomId:guid}")] + /// Id of the staff to retrieve room + /// A RoomDto of the retrieved room + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpGet("{staffId:guid}/rooms")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetByRoom([FromRoute] Guid roomId) + public async Task>> GetRoomByStaffId( + [FromRoute] Guid staffId) { - var query = new GetStaffByRoom.Query() + var query = new GetRoomByStaffId.Query() { - RoomId = roomId + StaffId = staffId, }; var result = await Mediator.Send(query); - return Ok(Result.Succeed(result)); + return Ok(Result.Succeed(result)); } /// @@ -54,6 +67,7 @@ public async Task>> GetByRoom([FromRoute] Guid roo /// /// Get all staffs query parameters /// A paginated list of StaffDto + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -73,7 +87,7 @@ public async Task>>> GetAllPaginated } /// - /// Add a staff + /// Assign a staff /// /// Add staff details /// A StaffDto of the added staff @@ -82,12 +96,15 @@ public async Task>>> GetAllPaginated [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> Add([FromBody] AddStaffRequest request) + public async Task>> Assign( + [FromBody] AddStaffRequest request) { - var command = new AddStaff.Command() + var currentUser = _currentUserService.GetCurrentUser(); + var command = new AssignStaff.Command() { + CurrentUser = currentUser, RoomId = request.RoomId, - UserId = request.UserId, + StaffId = request.StaffId, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); @@ -98,40 +115,22 @@ public async Task>> Add([FromBody] AddStaffRequest /// /// Id of the staff to be removed from room /// A StaffDto of the removed staff + [RequiresRole(IdentityData.Roles.Admin)] [HttpPut("{staffId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> RemoveFromRoom( + public async Task>> RemoveStaffFromRoom( [FromRoute] Guid staffId) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new RemoveStaffFromRoom.Command() { + CurrentUser = currentUser, StaffId = staffId, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } - - /// - /// Remove a staff - /// - /// Id of the staff to be removed - /// A StaffDto of the removed staff - [HttpDelete("{staffId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> Remove( - [FromRoute] Guid staffId) - { - var command = new RemoveStaff.Command() - { - StaffId = staffId - }; - - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } } \ No newline at end of file diff --git a/src/Api/Controllers/UsersController.cs b/src/Api/Controllers/UsersController.cs index 5b8efe5f..12ca4695 100644 --- a/src/Api/Controllers/UsersController.cs +++ b/src/Api/Controllers/UsersController.cs @@ -1,28 +1,44 @@ +using Api.Controllers.Payload.Requests; using Api.Controllers.Payload.Requests.Users; +using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; using Application.Identity; using Application.Users.Commands; using Application.Users.Queries; using Infrastructure.Identity.Authorization; +using MediatR; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers; public class UsersController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public UsersController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// /// Get a user by id /// /// Id of the user to be retrieved /// A UserDto of the retrieved user + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpGet("{userId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetById([FromRoute] Guid userId) { - var query = new GetUserById.Query + var role = _currentUserService.GetRole(); + var userDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetUserById.Query() { + UserRole = role, + UserDepartmentId = userDepartmentId, UserId = userId, }; var result = await Mediator.Send(query); @@ -43,7 +59,36 @@ public async Task>>> GetAllPaginated( { var query = new GetAllUsersPaginated.Query() { - DepartmentId = queryParameters.DepartmentId, + DepartmentIds = queryParameters.DepartmentIds, + Role = queryParameters.Role, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Get all employees in the same department + /// + /// Query parameters + /// A list of UserDtos + [RequiresRole(IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpGet("employees")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>>> GetAllEmployeesPaginated( + [FromQuery] GetAllEmployeesPaginatedQueryParameters queryParameters) + { + var departmentId = _currentUserService.GetDepartmentId(); + var query = new GetAllUsersPaginated.Query() + { + DepartmentIds = new []{ departmentId }, + Role = IdentityData.Roles.Employee, SearchTerm = queryParameters.SearchTerm, Page = queryParameters.Page, Size = queryParameters.Size, @@ -68,8 +113,10 @@ public async Task>>> GetAllPaginated( [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task>> Add([FromBody] AddUserRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new AddUser.Command() { + CurrentUser = currentUser, Username = request.Username, Email = request.Email, FirstName = request.FirstName, @@ -81,43 +128,33 @@ public async Task>> Add([FromBody] AddUserRequest r var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } - - /// - /// Enable a user - /// - /// Id of the user to be enabled - /// A UserDto of the enabled user - [RequiresRole(IdentityData.Roles.Admin)] - [HttpPost("enable/{userId:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> Enable([FromRoute] Guid userId) - { - var command = new EnableUser.Command() - { - UserId = userId - }; - var result = await Mediator.Send(command); - return Ok(Result.Succeed(result)); - } - + /// - /// Disable a user + /// Update a user /// - /// Id of the user to be disabled - /// A UserDto of the disabled user + /// Id of the user to be updated + /// Update user details + /// A UserDto of the updated user [RequiresRole(IdentityData.Roles.Admin)] - [HttpPut("disable/{userId:guid}")] + [HttpPut("{userId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Disable([FromRoute] Guid userId) + public async Task>> Update( + [FromRoute] Guid userId, + [FromBody] UpdateUserRequest request) { - var command = new DisableUser.Command() + var currentUser = _currentUserService.GetCurrentUser(); + var command = new UpdateUser.Command() { + CurrentUser = currentUser, UserId = userId, + FirstName = request.FirstName, + LastName = request.LastName, + Position = request.Position, + Role = request.Role, + IsActive = request.IsActive, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); @@ -126,24 +163,52 @@ public async Task>> Disable([FromRoute] Guid userId /// /// Update a user /// - /// Id of the user to be updated /// Update user details /// A UserDto of the updated user - [HttpPut("{userId:guid}")] + [RequiresRole(IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpPut("self")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> Update([FromRoute] Guid userId, [FromBody] UpdateUserRequest request) + public async Task>> UpdateSelf( + [FromBody] UpdateSelfRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); var command = new UpdateUser.Command() { - UserId = userId, + CurrentUser = currentUser, + UserId = currentUser.Id, FirstName = request.FirstName, LastName = request.LastName, - Position = request.Position, + Position = currentUser.Position, + Role = currentUser.Role, + IsActive = currentUser.IsActive, }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } -} + + /// + /// Get all user related logs paginated + /// + /// Get all users related logs query parameters + /// A paginated list of UserLogDto + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet("logs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task>>> GetAllLogsPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllUserLogsPaginated.Query() + { + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + UserId = queryParameters.ObjectId, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } +} \ No newline at end of file diff --git a/src/Api/Middlewares/ExceptionMiddleware.cs b/src/Api/Middlewares/ExceptionMiddleware.cs index c08e16ed..3a52aae5 100644 --- a/src/Api/Middlewares/ExceptionMiddleware.cs +++ b/src/Api/Middlewares/ExceptionMiddleware.cs @@ -100,7 +100,7 @@ private static async void HandleAuthenticationException(HttpContext context, Exc private static async void HandleUnauthorizedAccessException(HttpContext context, Exception ex) { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; + context.Response.StatusCode = StatusCodes.Status403Forbidden; await WriteExceptionMessageAsync(context, ex); } diff --git a/src/Api/Services/CurrentUserService.cs b/src/Api/Services/CurrentUserService.cs index 920c6361..143498a9 100644 --- a/src/Api/Services/CurrentUserService.cs +++ b/src/Api/Services/CurrentUserService.cs @@ -7,25 +7,32 @@ namespace Api.Services; public class CurrentUserService : ICurrentUserService { - private readonly IApplicationDbContext _context; + private readonly IApplicationDbContext _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; public CurrentUserService(IHttpContextAccessor httpContextAccessor, IApplicationDbContext context) { _httpContextAccessor = httpContextAccessor; - _context = context; + _dbContext = context; + } + + public Guid GetId() + { + var id = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.NameId))!.Value; + return Guid.Parse(id); } public string GetRole() { var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; + .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.Sub))!.Value; if (userName is null) { throw new UnauthorizedAccessException(); } - var user = _context.Users.FirstOrDefault(x => x.Username.Equals(userName)); + var user = _dbContext.Users.FirstOrDefault(x => x.Username.Equals(userName)); if (user is null) { @@ -35,37 +42,24 @@ public string GetRole() return user.Role; } - public string? GetDepartment() + public Guid GetDepartmentId() { - var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; - if (userName is null) - { - throw new UnauthorizedAccessException(); - } - - var user = _context.Users - .Include(x => x.Department) - .FirstOrDefault(x => x.Username.Equals(userName)); - - if (user is null) - { - throw new UnauthorizedAccessException(); - } - - return user.Department?.Name; + var claim = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals("departmentId")); + var id = claim?.Value; + return Guid.Parse(id!); } public User GetCurrentUser() { var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; + .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.Sub))!.Value; if (userName is null) { throw new UnauthorizedAccessException(); } - var user = _context.Users + var user = _dbContext.Users .Include(x => x.Department) .FirstOrDefault(x => x.Username.Equals(userName)); @@ -79,50 +73,35 @@ public User GetCurrentUser() public Guid? GetCurrentRoomForStaff() { - var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; - if (userName is null) + var userIdString = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.NameId)); + if (userIdString is null || !Guid.TryParse(userIdString.Value, out var userId)) { throw new UnauthorizedAccessException(); } - var staff = _context.Staffs + var staff = _dbContext.Staffs .Include(x => x.User) .Include(x => x.Room) - .FirstOrDefault(x => x.User.Username.Equals(userName)); - - if (staff is null) - { - throw new UnauthorizedAccessException(); - } + .FirstOrDefault(x => x.Id == userId); - return staff.Room!.Id; + return staff?.Room?.Id; } public Guid? GetCurrentDepartmentForStaff() { - var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; - if (userName is null) + var userIdString = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.NameId)); + if (userIdString is null || !Guid.TryParse(userIdString.Value, out var userId)) { throw new UnauthorizedAccessException(); } - var staff = _context.Staffs + var staff = _dbContext.Staffs .Include(x => x.User) .Include(x => x.Room) - .FirstOrDefault(x => x.User.Username.Equals(userName)); - - if (staff is null) - { - throw new UnauthorizedAccessException(); - } - - if (staff.Room is null) - { - throw new UnauthorizedAccessException(); - } + .FirstOrDefault(x => x.Id == userId); - return staff.Room!.DepartmentId; + return staff?.Room?.DepartmentId; } } \ No newline at end of file diff --git a/src/Api/Services/ExpiryPermissionService.cs b/src/Api/Services/ExpiryPermissionService.cs new file mode 100644 index 00000000..360c108e --- /dev/null +++ b/src/Api/Services/ExpiryPermissionService.cs @@ -0,0 +1,30 @@ +using Application.Common.Interfaces; +using Domain.Entities.Physical; +using NodaTime; + +namespace Api.Services; + +public class ExpiryPermissionService : BackgroundService +{ + private readonly IServiceProvider _serviceProvider; + + public ExpiryPermissionService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var expiredPermissions = context.Permissions.Where(x => x.ExpiryDateTime < localDateTimeNow); + context.Permissions.RemoveRange(expiredPermissions); + await context.SaveChangesAsync(stoppingToken); + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index f1789974..2f6bfb3b 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Application/Borrows/Commands/ApproveBorrowRequest.cs b/src/Application/Borrows/Commands/ApproveBorrowRequest.cs deleted file mode 100644 index 9d7b345d..00000000 --- a/src/Application/Borrows/Commands/ApproveBorrowRequest.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Statuses; -using MediatR; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace Application.Borrows.Commands; - -public class ApproveBorrowRequest -{ - public record Command : IRequest - { - public Guid BorrowId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var borrowRequest = await _context.Borrows - .Include(x => x.Borrower) - .Include(x => x.Document) - .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); - if (borrowRequest is null) - { - throw new KeyNotFoundException("Borrow request does not exist."); - } - - if (borrowRequest.Document.Status is DocumentStatus.Lost) - { - borrowRequest.Status = BorrowRequestStatus.NotProcessable; - _context.Borrows.Update(borrowRequest); - await _context.SaveChangesAsync(cancellationToken); - throw new ConflictException("Document is lost. Request is unprocessable."); - } - - if (borrowRequest.Status is not BorrowRequestStatus.Pending - && borrowRequest.Status is not BorrowRequestStatus.Rejected) - { - throw new ConflictException("Request cannot be approved."); - } - - var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); - var existedBorrow = await _context.Borrows - .FirstOrDefaultAsync(x => - x.Document.Id == borrowRequest.Document.Id - && x.Id != borrowRequest.Id - && ((x.DueTime > localDateTimeNow) - || x.Status == BorrowRequestStatus.Overdue), cancellationToken); - - if (existedBorrow is not null) - { - if (existedBorrow?.Status - is BorrowRequestStatus.Approved - or BorrowRequestStatus.CheckedOut - && borrowRequest.BorrowTime < existedBorrow.DueTime) - { - throw new ConflictException("This document cannot be borrowed."); - } - } - - borrowRequest.Status = BorrowRequestStatus.Approved; - var result = _context.Borrows.Update(borrowRequest); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs b/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs new file mode 100644 index 00000000..45120059 --- /dev/null +++ b/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs @@ -0,0 +1,156 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Enums; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class ApproveOrRejectBorrowRequest +{ + public record Command : IRequest + { + public Guid CurrentUserId { get; init; } + public Guid BorrowId { get; init; } + public string Decision { get; init; } = null!; + public string StaffReason { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(x => x.Folder!) + .ThenInclude(x => x.Locker) + .ThenInclude(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Document.Status is DocumentStatus.Lost) + { + borrowRequest.Status = BorrowRequestStatus.NotProcessable; + _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + throw new ConflictException("Document is lost. Request is unprocessable."); + } + + if (borrowRequest.Status is not (BorrowRequestStatus.Pending or BorrowRequestStatus.Rejected) + && request.Decision.IsApproval()) + { + throw new ConflictException("Request cannot be approved."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Pending + && request.Decision.IsRejection()) + { + throw new ConflictException("Request cannot be rejected."); + } + + var currentUser = await _context.Users + .FirstOrDefaultAsync(x => x.Id == request.CurrentUserId, cancellationToken); + + var staff = await _context.Staffs + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.CurrentUserId, cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + if (staff.Room is null) + { + throw new ConflictException("Staff does not manage a room."); + } + + if (staff.Room.Id != borrowRequest.Document.Folder!.Locker.Room.Id) + { + throw new ConflictException("Request cannot be checked out due to different room."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var existedBorrows = _context.Borrows + .Where(x => + x.Document.Id == borrowRequest.Document.Id + && x.Id != borrowRequest.Id + && (x.DueTime > localDateTimeNow + || x.Status == BorrowRequestStatus.Overdue)); + + var log = new DocumentLog() + { + ObjectId = borrowRequest.Document.Id, + UserId = currentUser!.Id, + User = currentUser, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.Approve, + }; + var requestLog = new RequestLog() + { + ObjectId = borrowRequest.Document.Id, + Type = RequestType.Borrow, + UserId = currentUser.Id, + User = currentUser, + Time = localDateTimeNow, + Action = RequestLogMessages.ApproveBorrow, + }; + + if (request.Decision.IsApproval()) + { + foreach (var existedBorrow in existedBorrows) + { + if ((existedBorrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut) + && (borrowRequest.BorrowTime <= existedBorrow.DueTime && borrowRequest.DueTime >= existedBorrow.BorrowTime)) + { + throw new ConflictException("Request cannot be approved."); + } + } + + borrowRequest.Status = BorrowRequestStatus.Approved; + } + + if (request.Decision.IsRejection()) + { + borrowRequest.Status = BorrowRequestStatus.Rejected; + log.Action = DocumentLogMessages.Borrow.Reject; + requestLog.Action = RequestLogMessages.RejectBorrow; + } + + borrowRequest.StaffReason = request.StaffReason; + borrowRequest.LastModified = localDateTimeNow; + borrowRequest.LastModifiedBy = currentUser.Id; + + var result = _context.Borrows.Update(borrowRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.RequestLogs.AddAsync(requestLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/BorrowDocument.cs b/src/Application/Borrows/Commands/BorrowDocument.cs index ad1df6fd..7fbf04d7 100644 --- a/src/Application/Borrows/Commands/BorrowDocument.cs +++ b/src/Application/Borrows/Commands/BorrowDocument.cs @@ -1,8 +1,13 @@ +using System.Runtime.InteropServices; using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; using AutoMapper; +using Domain.Entities.Logging; using Domain.Entities.Physical; +using Domain.Events; using Domain.Statuses; using FluentValidation; using MediatR; @@ -19,7 +24,7 @@ public Validator() { RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.Reason) + RuleFor(x => x.BorrowReason) .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); RuleFor(x => x.BorrowFrom) @@ -37,18 +42,22 @@ public record Command : IRequest public Guid BorrowerId { get; init; } public DateTime BorrowFrom { get; init; } public DateTime BorrowTo { get; init; } - public string Reason { get; init; } = null!; + public string BorrowReason { get; init; } = null!; } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IPermissionManager _permissionManager; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IPermissionManager permissionManager, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _permissionManager = permissionManager; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -73,6 +82,7 @@ public async Task Handle(Command request, CancellationToken cancellat var document = await _context.Documents .Include(x => x.Department) + .Include(x => x.Importer) .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); if (document is null) { @@ -93,29 +103,31 @@ public async Task Handle(Command request, CancellationToken cancellat // if the request is in time, meaning not overdue, // then check if its due date is less than the borrow request date, if not then check // if it's already been approved, checked out or lost, meaning - var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); - var existedBorrow = await _context.Borrows + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var borrowFromTime = LocalDateTime.FromDateTime(request.BorrowFrom); + var borrowToTime = LocalDateTime.FromDateTime(request.BorrowTo); + var existedBorrows = _context.Borrows .Include(x => x.Borrower) - .FirstOrDefaultAsync(x => + .Where(x => x.Document.Id == request.DocumentId - && ((x.DueTime > localDateTimeNow) - || x.Status == BorrowRequestStatus.Overdue), cancellationToken); - - if (existedBorrow is not null) + && (x.DueTime > localDateTimeNow + || x.Status == BorrowRequestStatus.Overdue)); + + foreach (var borrow in existedBorrows) { // Does not make sense if the same person go up and want to borrow the same document again // even if the borrow day will be after the due day - if (existedBorrow.Borrower.Id == request.BorrowerId - && existedBorrow.Status is BorrowRequestStatus.Pending - or BorrowRequestStatus.Approved) + if (borrow.Borrower.Id == request.BorrowerId + && borrow.Status is BorrowRequestStatus.Pending + or BorrowRequestStatus.Approved) { throw new ConflictException("This document is already requested borrow from the same user."); } - if (existedBorrow.Status - is BorrowRequestStatus.Approved - or BorrowRequestStatus.CheckedOut - && LocalDateTime.FromDateTime(request.BorrowFrom) < existedBorrow.DueTime) + if ((borrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut) + && (borrowFromTime <= borrow.DueTime && borrowToTime >= borrow.BorrowTime)) { throw new ConflictException("This document cannot be borrowed."); } @@ -125,13 +137,36 @@ or BorrowRequestStatus.CheckedOut { Borrower = user, Document = document, - BorrowTime = LocalDateTime.FromDateTime(request.BorrowFrom), - DueTime = LocalDateTime.FromDateTime(request.BorrowTo), - Reason = request.Reason, + BorrowTime = borrowFromTime, + DueTime = borrowToTime, + BorrowReason = request.BorrowReason, + StaffReason = string.Empty, Status = BorrowRequestStatus.Pending, + Created = localDateTimeNow, + CreatedBy = user.Id, + }; + + if (document.IsPrivate) + { + var isGranted = _permissionManager.IsGranted(request.DocumentId, DocumentOperation.Borrow, request.BorrowerId); + if (!isGranted) + { + throw new UnauthorizedAccessException("You don't have permission to borrow this document."); + } + entity.Status = BorrowRequestStatus.Approved; + } + + var log = new DocumentLog() + { + UserId = user.Id, + User = user, + ObjectId = document.Id, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.NewBorrowRequest, }; var result = await _context.Borrows.AddAsync(entity, cancellationToken); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); diff --git a/src/Application/Borrows/Commands/CancelBorrowRequest.cs b/src/Application/Borrows/Commands/CancelBorrowRequest.cs index 97f111d4..b4015ffd 100644 --- a/src/Application/Borrows/Commands/CancelBorrowRequest.cs +++ b/src/Application/Borrows/Commands/CancelBorrowRequest.cs @@ -1,10 +1,13 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities.Logging; using Domain.Statuses; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Borrows.Commands; @@ -12,6 +15,7 @@ public class CancelBorrowRequest { public record Command : IRequest { + public Guid CurrentUserId { get; init; } public Guid BorrowId { get; init; } } @@ -19,11 +23,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -37,13 +43,32 @@ public async Task Handle(Command request, CancellationToken cancellat throw new KeyNotFoundException("Borrow request does not exist."); } - if (borrowRequest.Status is not (BorrowRequestStatus.Approved or BorrowRequestStatus.Pending)) + if (borrowRequest.Status is not BorrowRequestStatus.Pending) { throw new ConflictException("Request cannot be cancelled."); } + if (borrowRequest.Borrower.Id != request.CurrentUserId) + { + throw new ConflictException("Can not cancel other borrow request"); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var currentUser = await _context.Users + .FirstOrDefaultAsync(x => x.Id == request.CurrentUserId, cancellationToken); + var log = new DocumentLog() + { + ObjectId = borrowRequest.Document.Id, + UserId = currentUser!.Id, + User = currentUser, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.CanCel, + }; + borrowRequest.Status = BorrowRequestStatus.Cancelled; var result = _context.Borrows.Update(borrowRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Borrows/Commands/CheckoutDocument.cs b/src/Application/Borrows/Commands/CheckoutDocument.cs index 6578225f..9ac4d77b 100644 --- a/src/Application/Borrows/Commands/CheckoutDocument.cs +++ b/src/Application/Borrows/Commands/CheckoutDocument.cs @@ -1,10 +1,14 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Statuses; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Borrows.Commands; @@ -12,6 +16,7 @@ public class CheckoutDocument { public record Command : IRequest { + public User CurrentStaff { get; init; } = null!; public Guid BorrowId { get; init; } } @@ -19,11 +24,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -31,7 +38,11 @@ public async Task Handle(Command request, CancellationToken cancellat var borrowRequest = await _context.Borrows .Include(x => x.Borrower) .Include(x => x.Document) + .ThenInclude(x => x.Folder!) + .ThenInclude(x => x.Locker) + .ThenInclude(x => x.Room) .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) { throw new KeyNotFoundException("Borrow request does not exist."); @@ -47,10 +58,41 @@ public async Task Handle(Command request, CancellationToken cancellat throw new ConflictException("Request cannot be checked out."); } + var staff = await _context.Staffs + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.CurrentStaff.Id, cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + if (staff.Room is null) + { + throw new ConflictException("Staff does not have a room."); + } + + if (staff.Room.Id != borrowRequest.Document.Folder!.Locker.Room.Id) + { + throw new ConflictException("Request cannot be checked out due to different room."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); borrowRequest.Status = BorrowRequestStatus.CheckedOut; borrowRequest.Document.Status = DocumentStatus.Borrowed; + borrowRequest.Document.LastModified = localDateTimeNow; + borrowRequest.Document.LastModifiedBy = request.CurrentStaff.Id; + var log = new DocumentLog() + { + ObjectId = borrowRequest.Document.Id, + UserId = request.CurrentStaff.Id, + User = request.CurrentStaff, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.Checkout, + }; var result = _context.Borrows.Update(borrowRequest); _context.Documents.Update(borrowRequest.Document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Borrows/Commands/RejectBorrowRequest.cs b/src/Application/Borrows/Commands/RejectBorrowRequest.cs deleted file mode 100644 index e1290e83..00000000 --- a/src/Application/Borrows/Commands/RejectBorrowRequest.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Statuses; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Borrows.Commands; - -public class RejectBorrowRequest -{ - public record Command : IRequest - { - public Guid BorrowId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var borrowRequest = await _context.Borrows - .Include(x => x.Borrower) - .Include(x => x.Document) - .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); - if (borrowRequest is null) - { - throw new KeyNotFoundException("Borrow request does not exist."); - } - - if (borrowRequest.Status is not BorrowRequestStatus.Pending) - { - throw new ConflictException("Request cannot be rejected."); - } - - borrowRequest.Status = BorrowRequestStatus.Rejected; - var result = _context.Borrows.Update(borrowRequest); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/ReturnDocument.cs b/src/Application/Borrows/Commands/ReturnDocument.cs index 918a979d..e18b5c89 100644 --- a/src/Application/Borrows/Commands/ReturnDocument.cs +++ b/src/Application/Borrows/Commands/ReturnDocument.cs @@ -1,7 +1,10 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Statuses; using MediatR; using Microsoft.EntityFrameworkCore; @@ -13,6 +16,7 @@ public class ReturnDocument { public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid DocumentId { get; init; } } @@ -20,11 +24,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -32,8 +38,11 @@ public async Task Handle(Command request, CancellationToken cancellat var borrowRequest = await _context.Borrows .Include(x => x.Borrower) .Include(x => x.Document) + .ThenInclude(x => x.Folder!) + .ThenInclude(x => x.Locker) + .ThenInclude(x => x.Room) .FirstOrDefaultAsync(x => x.Document.Id == request.DocumentId - && x.Status == BorrowRequestStatus.CheckedOut, cancellationToken); + && x.Status == BorrowRequestStatus.CheckedOut, cancellationToken); if (borrowRequest is null) { throw new KeyNotFoundException("Borrow request does not exist."); @@ -48,11 +57,42 @@ public async Task Handle(Command request, CancellationToken cancellat { throw new ConflictException("Request cannot be made."); } + + var staff = await _context.Staffs + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.CurrentUser.Id, cancellationToken); + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + if (staff.Room is null) + { + throw new ConflictException("Staff does not have a room."); + } + + if (staff.Room.Id != borrowRequest.Document.Folder!.Locker.Room.Id) + { + throw new ConflictException("Request cannot be checked out due to different room."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + borrowRequest.Status = BorrowRequestStatus.Returned; borrowRequest.Document.Status = DocumentStatus.Available; - borrowRequest.ActualReturnTime = LocalDateTime.FromDateTime(DateTime.Now); + borrowRequest.ActualReturnTime = localDateTimeNow; + + var log = new DocumentLog() + { + ObjectId = borrowRequest.Document.Id, + UserId = request.CurrentUser.Id, + User = request.CurrentUser, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.Checkout, + }; var result = _context.Borrows.Update(borrowRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); _context.Documents.Update(borrowRequest.Document); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); diff --git a/src/Application/Borrows/Commands/UpdateBorrow.cs b/src/Application/Borrows/Commands/UpdateBorrow.cs index fe39c471..c8cd900f 100644 --- a/src/Application/Borrows/Commands/UpdateBorrow.cs +++ b/src/Application/Borrows/Commands/UpdateBorrow.cs @@ -1,7 +1,10 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Domain.Statuses; using FluentValidation; @@ -19,7 +22,7 @@ public Validator() { RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.Reason) + RuleFor(x => x.BorrowReason) .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); RuleFor(x => x.BorrowFrom) @@ -33,21 +36,24 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid BorrowId { get; init; } public DateTime BorrowFrom { get; init; } public DateTime BorrowTo { get; init; } - public string Reason { get; init; } = null!; + public string BorrowReason { get; init; } = null!; } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -71,31 +77,48 @@ public async Task Handle(Command request, CancellationToken cancellat throw new ConflictException("Document is lost."); } - var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); - var existedBorrow = await _context.Borrows + if (borrowRequest.Borrower.Id != request.CurrentUser.Id) + { + throw new ConflictException("Can not update other borrow request."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var existedBorrows = _context.Borrows .Include(x => x.Borrower) - .FirstOrDefaultAsync(x => + .Where(x => x.Document.Id == borrowRequest.Document.Id && x.Id != borrowRequest.Id && ((x.DueTime > localDateTimeNow) - || x.Status == BorrowRequestStatus.Overdue), cancellationToken); - - if (existedBorrow is not null) + || x.Status == BorrowRequestStatus.Overdue)); + + var borrowFromTime = LocalDateTime.FromDateTime(request.BorrowFrom); + var borrowToTime = LocalDateTime.FromDateTime(request.BorrowTo); + foreach (var borrow in existedBorrows) { - if (existedBorrow.Status - is BorrowRequestStatus.Approved - or BorrowRequestStatus.CheckedOut - && LocalDateTime.FromDateTime(request.BorrowFrom) < existedBorrow.DueTime) + if ((borrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut) + && (borrowFromTime <= borrow.DueTime && borrowToTime >= borrow.BorrowTime)) { - throw new ConflictException("This document cannot be borrowed."); + throw new ConflictException("This document cannot be updated."); } } - - borrowRequest.BorrowTime = LocalDateTime.FromDateTime(request.BorrowFrom); - borrowRequest.DueTime = LocalDateTime.FromDateTime(request.BorrowTo); - borrowRequest.Reason = request.Reason; - + borrowRequest.BorrowTime = borrowFromTime; + borrowRequest.DueTime = borrowToTime; + borrowRequest.BorrowReason = request.BorrowReason; + borrowRequest.LastModified = localDateTimeNow; + borrowRequest.LastModifiedBy = request.CurrentUser.Id; + + var log = new DocumentLog() + { + UserId = request.CurrentUser.Id, + User = request.CurrentUser, + ObjectId = borrowRequest.Document.Id, + Time = localDateTimeNow, + Action = DocumentLogMessages.Borrow.Update, + }; var result = _context.Borrows.Update(borrowRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); diff --git a/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs index c78f674a..ee822637 100644 --- a/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs +++ b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs @@ -1,11 +1,11 @@ -using Application.Common.Exceptions; using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; using Domain.Statuses; -using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; @@ -13,17 +13,10 @@ namespace Application.Borrows.Queries; public class GetAllBorrowRequestsPaginated { - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - } - } - public record Query : IRequest> { - public Guid? DepartmentId { get; init; } + public User CurrentUser { get; init; } = null!; + public Guid? RoomId { get; init; } public Guid? DocumentId { get; init; } public Guid? EmployeeId { get; init; } public int? Page { get; init; } @@ -47,6 +40,47 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task> Handle(Query request, CancellationToken cancellationToken) { + if (request.CurrentUser.Role.IsStaff()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + + var room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + var roomDoesNotExist = room is null; + + if (roomDoesNotExist + || RoomIsNotInSameDepartment(request.CurrentUser, room!)) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + } + + if (request.CurrentUser.Role.IsEmployee()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + + if (request.EmployeeId != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("User can not access this resource"); + } + + var room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + var roomDoesNotExist = room is null; + + if (roomDoesNotExist + || RoomIsNotInSameDepartment(request.CurrentUser, room!)) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + } + var borrows = _context.Borrows.AsQueryable(); borrows = borrows @@ -59,9 +93,9 @@ public async Task> Handle(Query request, .ThenInclude(t => t.Room) .ThenInclude(s => s.Department); - if (request.DepartmentId is not null) + if (request.RoomId is not null) { - borrows = borrows.Where(x => x.Document.Department!.Id == request.DepartmentId); + borrows = borrows.Where(x => x.Document.Folder!.Locker.Room.Id == request.RoomId); } if (request.EmployeeId is not null) @@ -108,5 +142,8 @@ public async Task> Handle(Query request, return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); } + + private static bool RoomIsNotInSameDepartment(User user, Room room) + => user.Department?.Id != room.DepartmentId; } } \ No newline at end of file diff --git a/src/Application/Borrows/Queries/GetAllRequestLogsPaginated.cs b/src/Application/Borrows/Queries/GetAllRequestLogsPaginated.cs new file mode 100644 index 00000000..dc3176e8 --- /dev/null +++ b/src/Application/Borrows/Queries/GetAllRequestLogsPaginated.cs @@ -0,0 +1,58 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +public class GetAllRequestLogsPaginated +{ + public record Query : IRequest> + { + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var logs = _context.RequestLogs + .Include(x => x.ObjectId) + .AsQueryable(); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.Trim().ToLower().Contains(request.SearchTerm.Trim().ToLower())); + } + + var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; + var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; + + var count = await logs.CountAsync(cancellationToken); + var list = await logs + .OrderByDescending(x => x.Time) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } + } +} diff --git a/src/Application/Common/Extensions/QueryableExtensions.cs b/src/Application/Common/Extensions/QueryableExtensions.cs index a670cca7..71587465 100644 --- a/src/Application/Common/Extensions/QueryableExtensions.cs +++ b/src/Application/Common/Extensions/QueryableExtensions.cs @@ -1,4 +1,10 @@ using System.Linq.Expressions; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using AutoMapper; +using Domain.Common; +using Microsoft.EntityFrameworkCore; namespace Application.Common.Extensions; @@ -9,12 +15,12 @@ public static IQueryable OrderByCustom(this IQueryable Paginate(this IQueryable ite { return items.Skip((page - 1) * size).Take(size); } + + public static async Task> LoggingListPaginateAsync( + this IQueryable items, + int? page, + int? size, + IConfigurationProvider mapperConfiguration, + CancellationToken cancellationToken) + where TEntityDto : BaseDto, IMapFrom + where TLoggingEntity : BaseLoggingEntity + where TEntity : BaseEntity + { + var pageNumber = page is null or <= 0 ? 1 : page; + var sizeNumber = size is null or <= 0 ? 10 : size; + + var count = await items.CountAsync(cancellationToken); + var list = await items + .OrderByDescending(x => x.Time) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var mapper = mapperConfiguration.CreateMapper(); + var result = mapper.Map>(list); + + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } + + public static async Task> ListPaginateWithSortAsync( + this IQueryable items, + int? page, + int? size, + string? sortBy, + string? sortOrder, + IConfigurationProvider mapperConfiguration, + CancellationToken cancellationToken) + where TEntityDto : BaseDto, IMapFrom + { + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(BaseDto.Id); + } + + sortOrder ??= "asc"; + var pageNumber = page is null or <= 0 ? 1 : page; + var sizeNumber = size is null or <= 0 ? 10 : size; + + var count = await items.CountAsync(cancellationToken); + var list = await items + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var mapper = mapperConfiguration.CreateMapper(); + var result = mapper.Map>(list); + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } } \ No newline at end of file diff --git a/src/Application/Common/Extensions/StringExtensions.cs b/src/Application/Common/Extensions/StringExtensions.cs index b2a723de..07f9a6e5 100644 --- a/src/Application/Common/Extensions/StringExtensions.cs +++ b/src/Application/Common/Extensions/StringExtensions.cs @@ -1,3 +1,5 @@ +using Application.Identity; + namespace Application.Common.Extensions; public static class StringExtensions @@ -10,4 +12,20 @@ public static bool MatchesPropertyName(this string input) return properties.Any(property => string.Equals(property.Name, input)); } + + public static bool IsAdmin(this string role) + => role.Equals(IdentityData.Roles.Admin); + + public static bool IsStaff(this string role) + => role.Equals(IdentityData.Roles.Staff); + + public static bool IsEmployee(this string role) + => role.Equals(IdentityData.Roles.Employee); + + public static bool IsApproval(this string decision) + => decision.ToLower().Trim().Equals("approve"); + + public static bool IsRejection(this string decision) + => decision.ToLower().Trim().Equals("reject"); + } \ No newline at end of file diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs index 8f12f9bd..9176b655 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -1,5 +1,6 @@ using Domain.Entities; using Domain.Entities.Digital; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Microsoft.EntityFrameworkCore; @@ -15,11 +16,20 @@ public interface IApplicationDbContext public DbSet Lockers { get; } public DbSet Folders { get; } public DbSet Documents { get; } + public DbSet ImportRequests { get; } public DbSet Borrows { get; } + public DbSet Permissions { get; } public DbSet UserGroups { get; } public DbSet Files { get; } public DbSet Entries { get; } + + public DbSet RoomLogs { get; } + public DbSet LockerLogs { get; } + public DbSet FolderLogs { get; } + public DbSet DocumentLogs { get; } + public DbSet RequestLogs { get; } + public DbSet UserLogs { get; } Task SaveChangesAsync(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Application/Common/Interfaces/ICurrentUserService.cs b/src/Application/Common/Interfaces/ICurrentUserService.cs index 0c4aed4b..a5528245 100644 --- a/src/Application/Common/Interfaces/ICurrentUserService.cs +++ b/src/Application/Common/Interfaces/ICurrentUserService.cs @@ -4,8 +4,9 @@ namespace Application.Common.Interfaces; public interface ICurrentUserService { + Guid GetId(); string GetRole(); - string? GetDepartment(); + Guid GetDepartmentId(); User GetCurrentUser(); Guid? GetCurrentRoomForStaff(); Guid? GetCurrentDepartmentForStaff(); diff --git a/src/Application/Common/Interfaces/IDateTimeProvider.cs b/src/Application/Common/Interfaces/IDateTimeProvider.cs new file mode 100644 index 00000000..379ff470 --- /dev/null +++ b/src/Application/Common/Interfaces/IDateTimeProvider.cs @@ -0,0 +1,6 @@ +namespace Application.Common.Interfaces; + +public interface IDateTimeProvider +{ + public DateTime DateTimeNow { get; } +} \ No newline at end of file diff --git a/src/Application/Common/Interfaces/IPermissionManager.cs b/src/Application/Common/Interfaces/IPermissionManager.cs new file mode 100644 index 00000000..4e7c32fc --- /dev/null +++ b/src/Application/Common/Interfaces/IPermissionManager.cs @@ -0,0 +1,12 @@ +using Application.Common.Models.Operations; +using Domain.Entities; +using Domain.Entities.Physical; + +namespace Application.Common.Interfaces; + +public interface IPermissionManager +{ + bool IsGranted(Guid documentId, DocumentOperation operation, params Guid[] userIds); + Task GrantAsync(Document document, DocumentOperation operation, User[] users, DateTime expiryDate, CancellationToken cancellationToken); + Task RevokeAsync(Guid documentId, DocumentOperation operation, Guid[] userIds, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Application/Common/Messages/DocumentLogMessages.cs b/src/Application/Common/Messages/DocumentLogMessages.cs new file mode 100644 index 00000000..e92040f2 --- /dev/null +++ b/src/Application/Common/Messages/DocumentLogMessages.cs @@ -0,0 +1,30 @@ +namespace Application.Common.Messages; + +public static class DocumentLogMessages +{ + public static class Import + { + public const string NewImport = "Imported new document"; + public const string NewImportRequest = "Created new import request"; + public const string Checkin = "Checked in document"; + public const string Approve = "Document is approved to be imported"; + public const string Reject = "Rejected import request"; + public const string Assign = "Assigned to a folder"; + } + public static class Borrow + { + public const string NewBorrowRequest = "Created new borrow request"; + public const string CanCel = "Cancelled borrow request"; + public const string Approve = "Approved borrow request"; + public const string Reject = "Rejected borrow request"; + public const string Checkout = "Checked out borrow request"; + public const string Return = "Returned borrow request"; + public const string Update = "Updated borrow request"; + } + public const string Delete = "Delete document"; + public const string Update = "Updated document information"; + public static string GrantRead(string userName) => $"Share Read Permission to user {userName}"; + public static string GrantBorrow(string userName) => $"Share Borrow Permission to user {userName}"; + public static string RevokeRead(string userName) => $"Remove Read Permission to user {userName}"; + public static string RevokeBorrow(string userName) => $"Remove Borrow Permission to user {userName}"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/FolderLogMessage.cs b/src/Application/Common/Messages/FolderLogMessage.cs new file mode 100644 index 00000000..c2bffb4d --- /dev/null +++ b/src/Application/Common/Messages/FolderLogMessage.cs @@ -0,0 +1,9 @@ +namespace Application.Common.Messages; + +public static class FolderLogMessage +{ + public const string Add = "Added folder"; + public const string Update = "Updated folder"; + public const string Remove = "Removed folder"; + public const string AssignDocument = "Assigned document to folder"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/LockerLogMessage.cs b/src/Application/Common/Messages/LockerLogMessage.cs new file mode 100644 index 00000000..291e4510 --- /dev/null +++ b/src/Application/Common/Messages/LockerLogMessage.cs @@ -0,0 +1,8 @@ +namespace Application.Common.Messages; + +public static class LockerLogMessage +{ + public const string Add = "Added locker"; + public const string Update = "Updated locker"; + public const string Remove = "Removed locker"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/RequestLogMessages.cs b/src/Application/Common/Messages/RequestLogMessages.cs new file mode 100644 index 00000000..a84fbc04 --- /dev/null +++ b/src/Application/Common/Messages/RequestLogMessages.cs @@ -0,0 +1,10 @@ +namespace Application.Common.Messages; + +public static class RequestLogMessages +{ + public const string ApproveImport = "Approved import request"; + public const string RejectImport = "Rejected import request"; + public const string ApproveBorrow = "Rejected borrow request"; + public const string RejectBorrow = "Rejected borrow request"; + public const string CheckInImport = "Checkin import request"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/RoomLogMessage.cs b/src/Application/Common/Messages/RoomLogMessage.cs new file mode 100644 index 00000000..90125821 --- /dev/null +++ b/src/Application/Common/Messages/RoomLogMessage.cs @@ -0,0 +1,8 @@ +namespace Application.Common.Messages; + +public static class RoomLogMessage +{ + public const string Add = "Added room"; + public const string Update = "Updated room"; + public const string Remove = "Removed room"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/UserLogMessages.cs b/src/Application/Common/Messages/UserLogMessages.cs new file mode 100644 index 00000000..5599cb71 --- /dev/null +++ b/src/Application/Common/Messages/UserLogMessages.cs @@ -0,0 +1,16 @@ +namespace Application.Common.Messages; + +public static class UserLogMessages +{ + public static string Add(string role) => $"Added user with role {role}"; + public const string Update = "Updated user"; + public const string Disable = "Disabled user"; + + public static class Staff + { + public const string AddStaff = "Added a new staff"; + public static string AssignStaff(string roomId) => $"Assigned user to be staff of room {roomId}"; + public const string RemoveFromRoom = "Removed staff from room"; + public const string Remove = "Removed staff"; + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/BaseDto.cs b/src/Application/Common/Models/Dtos/BaseDto.cs new file mode 100644 index 00000000..28da7387 --- /dev/null +++ b/src/Application/Common/Models/Dtos/BaseDto.cs @@ -0,0 +1,6 @@ +namespace Application.Common.Models.Dtos; + +public class BaseDto +{ + public Guid Id { get; set; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/DepartmentDto.cs b/src/Application/Common/Models/Dtos/DepartmentDto.cs index 9ae2e4e6..2a22ab44 100644 --- a/src/Application/Common/Models/Dtos/DepartmentDto.cs +++ b/src/Application/Common/Models/Dtos/DepartmentDto.cs @@ -1,19 +1,11 @@ using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; using AutoMapper; using Domain.Entities; namespace Application.Common.Models.Dtos; -public class DepartmentDto : IMapFrom +public class DepartmentDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } = null!; - public Guid? RoomId { get; set; } - - public void Mapping(Profile profile) - { - profile.CreateMap() - .ForMember(dest => dest.RoomId, - opt => opt.MapFrom(src => src.Room!.Id)); - } } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Digital/EntryDto.cs b/src/Application/Common/Models/Dtos/Digital/EntryDto.cs index fe5445ab..b3882a91 100644 --- a/src/Application/Common/Models/Dtos/Digital/EntryDto.cs +++ b/src/Application/Common/Models/Dtos/Digital/EntryDto.cs @@ -3,9 +3,8 @@ namespace Application.Common.Models.Dtos.Digital; -public class EntryDto : IMapFrom +public class EntryDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } = null!; public string Path { get; set; } = null!; public FileDto? File { get; set; } diff --git a/src/Application/Common/Models/Dtos/Digital/FileDto.cs b/src/Application/Common/Models/Dtos/Digital/FileDto.cs index cf2f7531..c23b2383 100644 --- a/src/Application/Common/Models/Dtos/Digital/FileDto.cs +++ b/src/Application/Common/Models/Dtos/Digital/FileDto.cs @@ -3,8 +3,7 @@ namespace Application.Common.Models.Dtos.Digital; -public class FileDto : IMapFrom +public class FileDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string FileType { get; set; } = null!; } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Digital/UserGroupDto.cs b/src/Application/Common/Models/Dtos/Digital/UserGroupDto.cs index 88fb1d13..3707c15c 100644 --- a/src/Application/Common/Models/Dtos/Digital/UserGroupDto.cs +++ b/src/Application/Common/Models/Dtos/Digital/UserGroupDto.cs @@ -3,8 +3,7 @@ namespace Application.Common.Models.Dtos.Digital; -public class UserGroupDto : IMapFrom +public class UserGroupDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } = null!; } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/ImportRequestDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/ImportRequestDto.cs new file mode 100644 index 00000000..d05b3774 --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/ImportRequestDto.cs @@ -0,0 +1,22 @@ +using Application.Common.Mappings; +using AutoMapper; +using Domain.Entities.Physical; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class ImportRequestDto : BaseDto, IMapFrom +{ + public IssuedRequestRoomDto Room { get; set; } = null!; + public IssuedDocumentDto Document { get; set; } = null!; + public string ImportReason { get; set; } = null!; + public string StaffReason { get; set; } = null!; + public string Status { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())); + + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs new file mode 100644 index 00000000..a16aa23f --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs @@ -0,0 +1,24 @@ +using Application.Common.Mappings; +using AutoMapper; +using Domain.Entities.Physical; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class IssuedDocumentDto : BaseDto, IMapFrom +{ + public string Title { get; set; } = null!; + public string? Description { get; set; } + public string DocumentType { get; set; } = null!; + public IssuerDto? Issuer { get; set; } + public string Status { get; set; } = null!; + public bool IsPrivate { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())) + .ForMember(dest => dest.Issuer, + opt => opt.MapFrom(x => x.Importer)); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/IssuedRequestRoomDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/IssuedRequestRoomDto.cs new file mode 100644 index 00000000..6800b193 --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/IssuedRequestRoomDto.cs @@ -0,0 +1,21 @@ +using Application.Common.Mappings; +using AutoMapper; +using Domain.Entities.Physical; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class IssuedRequestRoomDto : BaseDto, IMapFrom +{ + public string Name { get; set; } = null!; + public string? Description { get; set; } + public Guid? StaffId { get; set; } + public DepartmentDto? Department { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.StaffId, + opt => opt.MapFrom(src => src.Staff!.Id)); + + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs new file mode 100644 index 00000000..bb812d7e --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs @@ -0,0 +1,16 @@ +using Application.Common.Mappings; +using Domain.Entities; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class IssuerDto : BaseDto, IMapFrom +{ + public string Username { get; set; } + public string Email { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Role { get; set; } + public string Position { get; set; } + public bool IsActive { get; set; } + public bool IsActivated { get; set; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/DocumentLogDto.cs b/src/Application/Common/Models/Dtos/Logging/DocumentLogDto.cs new file mode 100644 index 00000000..c7098277 --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/DocumentLogDto.cs @@ -0,0 +1,25 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class DocumentLogDto : BaseDto, IMapFrom +{ + public Guid UserId { get; set; } + public string Action { get; set; } + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } + + public void Mapping(Profile profile) + { + + profile.CreateMap() + .ForMember(dest => dest.Time, + opt => opt.MapFrom(src => src.Time.ToDateTimeUnspecified())); + + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/FolderLogDto.cs b/src/Application/Common/Models/Dtos/Logging/FolderLogDto.cs new file mode 100644 index 00000000..f5287aea --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/FolderLogDto.cs @@ -0,0 +1,22 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class FolderLogDto : BaseDto, IMapFrom +{ + public string Action { get; set; } = null!; + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Time, + opt => opt.MapFrom(src => src.Time.ToDateTimeUnspecified())); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/LockerLogDto.cs b/src/Application/Common/Models/Dtos/Logging/LockerLogDto.cs new file mode 100644 index 00000000..dc532aa7 --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/LockerLogDto.cs @@ -0,0 +1,22 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class LockerLogDto : BaseDto, IMapFrom +{ + public string Action { get; set; } = null!; + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember( dest => dest.Time, + opt => opt.MapFrom( src => src.Time.ToDateTimeUnspecified())); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/RequestLogDto.cs b/src/Application/Common/Models/Dtos/Logging/RequestLogDto.cs new file mode 100644 index 00000000..a874bd7a --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/RequestLogDto.cs @@ -0,0 +1,25 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class RequestLogDto : BaseDto, IMapFrom +{ + public string Action { get; set; } = null!; + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } = null!; + public string Type { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Time, + opt => opt.MapFrom(src => src.Time.ToDateTimeUnspecified())) + .ForMember(dest => dest.Type, + opt => opt.MapFrom(src => src.Type.ToString())); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/RoomLogDto.cs b/src/Application/Common/Models/Dtos/Logging/RoomLogDto.cs new file mode 100644 index 00000000..8d05216a --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/RoomLogDto.cs @@ -0,0 +1,25 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class RoomLogDto : BaseDto, IMapFrom +{ + public Guid UserId { get; set; } + public string Action { get; set; } + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } + + public void Mapping(Profile profile) + { + + profile.CreateMap() + .ForMember(dest => dest.Time, + opt => opt.MapFrom(src => src.Time.ToDateTimeUnspecified())); + + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Logging/UserLogDto.cs b/src/Application/Common/Models/Dtos/Logging/UserLogDto.cs new file mode 100644 index 00000000..83a1d38a --- /dev/null +++ b/src/Application/Common/Models/Dtos/Logging/UserLogDto.cs @@ -0,0 +1,22 @@ +using Application.Common.Mappings; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Logging; + +namespace Application.Common.Models.Dtos.Logging; + +public class UserLogDto : BaseDto, IMapFrom +{ + public string Action { get; set; } = null!; + public Guid? ObjectId { get; set; } + public DateTime Time { get; set; } + public UserDto User { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember( dest => dest.Time, + opt => opt.MapFrom( src => src.Time.ToDateTimeUnspecified())); + + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs b/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs index 49afc176..8199fa36 100644 --- a/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs @@ -5,15 +5,15 @@ namespace Application.Common.Models.Dtos.Physical; -public class BorrowDto : IMapFrom +public class BorrowDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public Guid BorrowerId { get; set; } public Guid DocumentId { get; set; } public DateTime BorrowTime { get; set; } public DateTime DueTime { get; set; } public DateTime ActualReturnTime { get; set; } - public string Reason { get; set; } = null!; + public string BorrowReason { get; set; } = null!; + public string StaffReason { get; set; } = null!; public string Status { get; set; } = null!; public void Mapping(Profile profile) diff --git a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs index 6c517ce1..6d383334 100644 --- a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs @@ -6,9 +6,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class DocumentDto : IMapFrom +public class DocumentDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Title { get; set; } = null!; public string? Description { get; set; } public string DocumentType { get; set; } = null!; @@ -16,6 +15,7 @@ public class DocumentDto : IMapFrom public UserDto? Importer { get; set; } public FolderDto? Folder { get; set; } public string Status { get; set; } = null!; + public bool IsPrivate { get; set; } public EntryDto? Entry { get; set; } public void Mapping(Profile profile) diff --git a/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs b/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs index 7420ad90..1b1e6fb9 100644 --- a/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs @@ -4,9 +4,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class DocumentItemDto : IMapFrom +public class DocumentItemDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Title { get; set; } = null!; public string? Description { get; set; } public string DocumentType { get; set; } = null!; diff --git a/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs b/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs index 10e77646..b05091d7 100644 --- a/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs @@ -4,7 +4,7 @@ namespace Application.Common.Models.Dtos.Physical; -public class EmptyFolderDto : IMapFrom +public class EmptyFolderDto : BaseDto, IMapFrom { public Guid Id { get; set; } public string Name { get; set; } diff --git a/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs b/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs index 6d7537c7..13135db4 100644 --- a/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs @@ -4,9 +4,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class EmptyLockerDto : IMapFrom +public class EmptyLockerDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } public int Capacity { get; set; } diff --git a/src/Application/Common/Models/Dtos/Physical/FolderDto.cs b/src/Application/Common/Models/Dtos/Physical/FolderDto.cs index 8460746b..87acaa71 100644 --- a/src/Application/Common/Models/Dtos/Physical/FolderDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/FolderDto.cs @@ -3,9 +3,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class FolderDto : IMapFrom +public class FolderDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } public LockerDto Locker { get; set; } diff --git a/src/Application/Common/Models/Dtos/Physical/LockerDto.cs b/src/Application/Common/Models/Dtos/Physical/LockerDto.cs index e7c9d975..6f44a7da 100644 --- a/src/Application/Common/Models/Dtos/Physical/LockerDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/LockerDto.cs @@ -3,9 +3,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class LockerDto : IMapFrom +public class LockerDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } public RoomDto Room { get; set; } diff --git a/src/Application/Common/Models/Dtos/Physical/PermissionDto.cs b/src/Application/Common/Models/Dtos/Physical/PermissionDto.cs new file mode 100644 index 00000000..b9cc456d --- /dev/null +++ b/src/Application/Common/Models/Dtos/Physical/PermissionDto.cs @@ -0,0 +1,24 @@ +using Application.Common.Mappings; +using Application.Common.Models.Operations; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Physical; + +namespace Application.Common.Models.Dtos.Physical; + +public class PermissionDto : IMapFrom +{ + public bool CanRead { get; set; } + public bool CanBorrow { get; set; } + public Guid EmployeeId { get; set; } + public Guid DocumentId { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.CanRead, + opt => opt.MapFrom(src => src.AllowedOperations.Contains(DocumentOperation.Read.ToString()))) + .ForMember(dest => dest.CanBorrow, + opt => opt.MapFrom(src => src.AllowedOperations.Contains(DocumentOperation.Borrow.ToString()))); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/RoomDto.cs b/src/Application/Common/Models/Dtos/Physical/RoomDto.cs index 44fe55e0..ce9be4e2 100644 --- a/src/Application/Common/Models/Dtos/Physical/RoomDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/RoomDto.cs @@ -5,9 +5,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class RoomDto : IMapFrom +public class RoomDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Name { get; set; } = null!; public string? Description { get; set; } public Guid? StaffId { get; set; } diff --git a/src/Application/Common/Models/Dtos/Physical/StaffDto.cs b/src/Application/Common/Models/Dtos/Physical/StaffDto.cs index cc6394f4..62ec45dc 100644 --- a/src/Application/Common/Models/Dtos/Physical/StaffDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/StaffDto.cs @@ -5,9 +5,8 @@ namespace Application.Common.Models.Dtos.Physical; -public class StaffDto : IMapFrom +public class StaffDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public UserDto User { get; set; } = null!; public RoomDto? Room { get; set; } diff --git a/src/Application/Common/Models/Dtos/UserDto.cs b/src/Application/Common/Models/Dtos/UserDto.cs index 3fe39e96..737b6e77 100644 --- a/src/Application/Common/Models/Dtos/UserDto.cs +++ b/src/Application/Common/Models/Dtos/UserDto.cs @@ -6,9 +6,8 @@ namespace Application.Users.Queries; -public class UserDto : IMapFrom +public class UserDto : BaseDto, IMapFrom { - public Guid Id { get; set; } public string Username { get; set; } public string Email { get; set; } public string FirstName { get; set; } diff --git a/src/Application/Common/Models/Operations/DocumentOperation.cs b/src/Application/Common/Models/Operations/DocumentOperation.cs new file mode 100644 index 00000000..da6faa45 --- /dev/null +++ b/src/Application/Common/Models/Operations/DocumentOperation.cs @@ -0,0 +1,7 @@ +namespace Application.Common.Models.Operations; + +public enum DocumentOperation +{ + Read, + Borrow, +} \ No newline at end of file diff --git a/src/Application/Departments/Commands/AddDepartment.cs b/src/Application/Departments/Commands/AddDepartment.cs index 525d83c4..a057d0d7 100644 --- a/src/Application/Departments/Commands/AddDepartment.cs +++ b/src/Application/Departments/Commands/AddDepartment.cs @@ -39,7 +39,7 @@ public async Task Handle(Command request, CancellationToken cance var entity = new Department { - Name = request.Name + Name = request.Name, }; var result = await _context.Departments.AddAsync(entity, cancellationToken); diff --git a/src/Application/Departments/Commands/UpdateDepartment.cs b/src/Application/Departments/Commands/UpdateDepartment.cs deleted file mode 100644 index a080e22d..00000000 --- a/src/Application/Departments/Commands/UpdateDepartment.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Application.Common.Models.Dtos; -using Application.Users.Queries; -using MediatR; - -namespace Application.Departments.Commands; - -public class UpdateDepartment -{ - public record Command : IRequest - { - public Guid DepartmentId { get; set; } - public string Name { get; init; } = null!; - } -} \ No newline at end of file diff --git a/src/Application/Departments/Queries/GetDepartmentById.cs b/src/Application/Departments/Queries/GetDepartmentById.cs index 83f3f725..0073a6e0 100644 --- a/src/Application/Departments/Queries/GetDepartmentById.cs +++ b/src/Application/Departments/Queries/GetDepartmentById.cs @@ -1,6 +1,7 @@ using Application.Common.Interfaces; using Application.Common.Models.Dtos; using Application.Common.Models.Dtos.Physical; +using Application.Identity; using AutoMapper; using MediatR; using Microsoft.EntityFrameworkCore; @@ -11,6 +12,8 @@ public class GetDepartmentById { public record Query : IRequest { + public string UserRole { get; init; } = null!; + public Guid UserDepartmentId { get; init; } public Guid DepartmentId { get; init; } } public class QueryHandler : IRequestHandler @@ -26,6 +29,12 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task Handle(Query request, CancellationToken cancellationToken) { + if ((request.UserRole.Equals(IdentityData.Roles.Staff) || request.UserRole.Equals(IdentityData.Roles.Employee)) + && request.UserDepartmentId != request.DepartmentId) + { + throw new UnauthorizedAccessException("User cannot access this department."); + } + var department = await _context.Departments.FirstOrDefaultAsync(x => x.Id.Equals(request.DepartmentId), cancellationToken); if (department is null) diff --git a/src/Application/Documents/Commands/DeleteDocument.cs b/src/Application/Documents/Commands/DeleteDocument.cs index 903b7804..b5455f63 100644 --- a/src/Application/Documents/Commands/DeleteDocument.cs +++ b/src/Application/Documents/Commands/DeleteDocument.cs @@ -1,8 +1,12 @@ using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Documents.Commands; @@ -10,6 +14,7 @@ public class DeleteDocument { public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid DocumentId { get; init; } } @@ -17,33 +22,44 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { var document = await _context.Documents .Include( x => x.Folder) - .FirstOrDefaultAsync(x => x.Id.Equals(request.DocumentId), cancellationToken); + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); if (document is null) { throw new KeyNotFoundException("Document does not exist."); } - var folder = document.Folder; - var result = _context.Documents.Remove(document); + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); - if (folder is not null) + if (document.Folder is not null) { - folder.NumberOfDocuments -= 1; - _context.Folders.Update(folder); + document.Folder.NumberOfDocuments -= 1; + _context.Folders.Update(document.Folder); } + var log = new DocumentLog() + { + ObjectId = document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = DocumentLogMessages.Delete, + }; + var result = _context.Documents.Remove(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Documents/Commands/ImportDocument.cs b/src/Application/Documents/Commands/ImportDocument.cs index d33d4942..a60f64f2 100644 --- a/src/Application/Documents/Commands/ImportDocument.cs +++ b/src/Application/Documents/Commands/ImportDocument.cs @@ -1,11 +1,16 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Domain.Statuses; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Documents.Commands; @@ -13,26 +18,36 @@ public class ImportDocument { public record Command : IRequest { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public string Title { get; init; } = null!; public string? Description { get; init; } public string DocumentType { get; init; } = null!; public Guid ImporterId { get; init; } public Guid FolderId { get; init; } + public bool IsPrivate { get; init; } } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { + if (request.CurrentStaffRoomId is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + var importer = await _context.Users .Include(x => x.Department) .FirstOrDefaultAsync(x => x.Id == request.ImporterId, cancellationToken); @@ -41,6 +56,16 @@ public async Task Handle(Command request, CancellationToken cancell throw new KeyNotFoundException("User does not exist."); } + if (importer.Department is null) + { + throw new ConflictException("User does not have a department."); + } + + if (importer.Department.Id != request.CurrentUser.Department!.Id) + { + throw new ConflictException("User is in another department as staff."); + } + var document = _context.Documents.FirstOrDefault(x => x.Title.Trim().ToLower().Equals(request.Title.Trim().ToLower()) && x.Importer != null @@ -51,6 +76,8 @@ public async Task Handle(Command request, CancellationToken cancell } var folder = await _context.Folders + .Include(x => x.Locker) + .ThenInclude(y => y.Room) .FirstOrDefaultAsync(x => x.Id == request.FolderId, cancellationToken); if (folder is null) { @@ -62,6 +89,13 @@ public async Task Handle(Command request, CancellationToken cancell throw new ConflictException("This folder cannot accept more documents."); } + if (folder.Locker.Room.Id != request.CurrentStaffRoomId) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var entity = new Document() { Title = request.Title.Trim(), @@ -70,12 +104,24 @@ public async Task Handle(Command request, CancellationToken cancell Importer = importer, Department = importer.Department, Folder = folder, - Status = DocumentStatus.Issued, + Status = DocumentStatus.Available, + IsPrivate = request.IsPrivate, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + var log = new DocumentLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = entity.Id, + Time = localDateTimeNow, + Action = DocumentLogMessages.Import.NewImport, }; var result = await _context.Documents.AddAsync(entity, cancellationToken); folder.NumberOfDocuments += 1; _context.Folders.Update(folder); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Documents/Commands/ShareDocument.cs b/src/Application/Documents/Commands/ShareDocument.cs new file mode 100644 index 00000000..7b40ad74 --- /dev/null +++ b/src/Application/Documents/Commands/ShareDocument.cs @@ -0,0 +1,152 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Documents.Commands; + +public class ShareDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + public Guid UserId { get; init; } + public bool CanRead { get; init; } + public bool CanBorrow { get; init; } + public DateTime ExpiryDate { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _applicationDbContext; + private readonly IMapper _mapper; + private readonly IPermissionManager _permissionManager; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext applicationDbContext, IMapper mapper, IPermissionManager permissionManager, IDateTimeProvider dateTimeProvider) + { + _applicationDbContext = applicationDbContext; + _mapper = mapper; + _permissionManager = permissionManager; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await + _applicationDbContext.Documents + .Include(x => x.Importer) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.Importer!.Id != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("You are not the owner of the document."); + } + + var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Id == request.UserId, cancellationToken); + + if (user is null) + { + throw new KeyNotFoundException("User does not exist."); + } + + if (request.ExpiryDate.ToUniversalTime() < DateTime.UtcNow) + { + throw new ConflictException("Expiry date cannot be in the past."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var log = new DocumentLog() + { + ObjectId = document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = string.Empty, + }; + + await HandlePermissionGrantOrRevoke(request.CanRead, document, DocumentOperation.Read, user, request.ExpiryDate.ToLocalTime(), cancellationToken, log); + await HandlePermissionGrantOrRevoke(request.CanBorrow, document, DocumentOperation.Borrow, user, request.ExpiryDate.ToLocalTime(), cancellationToken, log); + + if (!string.IsNullOrEmpty(log.Action)) + { + await _applicationDbContext.DocumentLogs.AddAsync(log, cancellationToken); + } + await _applicationDbContext.SaveChangesAsync(cancellationToken); + return _mapper.Map(document); + } + + private async Task HandlePermissionGrantOrRevoke( + bool canPerformAction, + Document document, + DocumentOperation operation, + User user, + DateTime expiryDate, + CancellationToken cancellationToken, + DocumentLog log) + { + var isGranted = _permissionManager.IsGranted(document.Id, operation, user.Id); + + if (canPerformAction && !isGranted) + { + await GrantPermission(document, operation, user, expiryDate, log, cancellationToken); + } + + if (!canPerformAction && isGranted) + { + await RevokePermission(document, operation, user, log, cancellationToken); + } + } + + private async Task GrantPermission( + Document document, + DocumentOperation operation, + User user, + DateTime expiryDate, + DocumentLog log, + CancellationToken cancellationToken) + { + await _permissionManager.GrantAsync(document, operation, new[] { user }, expiryDate, cancellationToken); + + // log + log.Action = operation switch + { + DocumentOperation.Read => DocumentLogMessages.GrantRead(user.Username), + DocumentOperation.Borrow => DocumentLogMessages.GrantBorrow(user.Username), + _ => log.Action + }; + } + + private async Task RevokePermission( + Document document, + DocumentOperation operation, + User user, + DocumentLog log, + CancellationToken cancellationToken) + { + await _permissionManager.RevokeAsync(document.Id, operation, new[] { user.Id }, cancellationToken); + + // log + log.Action = operation switch + { + DocumentOperation.Read => DocumentLogMessages.RevokeRead(user.Username), + DocumentOperation.Borrow => DocumentLogMessages.RevokeBorrow(user.Username), + _ => log.Action + }; + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/UpdateDocument.cs b/src/Application/Documents/Commands/UpdateDocument.cs index d670ed86..195cc593 100644 --- a/src/Application/Documents/Commands/UpdateDocument.cs +++ b/src/Application/Documents/Commands/UpdateDocument.cs @@ -1,10 +1,16 @@ using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Documents.Commands; @@ -31,33 +37,43 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid DocumentId { get; init; } public string Title { get; init; } = null!; public string? Description { get; init; } public string DocumentType { get; init; } = null!; + public bool IsPrivate { get; init; } } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { var document = await _context.Documents + .Include(x => x.Department) .Include( x => x.Importer) - .FirstOrDefaultAsync( x => x.Id.Equals(request.DocumentId), cancellationToken); + .FirstOrDefaultAsync( x => x.Id == request.DocumentId, cancellationToken); if (document is null) { throw new KeyNotFoundException("Document does not exist."); } + + if (ViolateConstraints(request.CurrentUser, document)) + { + throw new UnauthorizedAccessException("Cannot update this document."); + } if (document.Importer is not null) { @@ -66,22 +82,41 @@ public async Task Handle(Command request, CancellationToken cancell .AnyAsync(x => x.Title.Trim().ToLower().Equals(request.Title.Trim().ToLower()) && x.Id != document.Id - && x.Importer!.Id == document.Importer!.Id - , cancellationToken); + && x.Importer!.Id == document.Importer!.Id, cancellationToken); if (titleExisted) { throw new ConflictException("Document name already exists for this importer."); } } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); document.Title = request.Title; document.DocumentType = request.DocumentType; document.Description = request.Description; + document.IsPrivate = request.IsPrivate; + document.LastModified = localDateTimeNow; + document.LastModifiedBy = request.CurrentUser.Id; + var log = new DocumentLog() + { + ObjectId = document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = DocumentLogMessages.Update, + }; var result = _context.Documents.Update(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private static bool ViolateConstraints(User currentUser, Document document) + => (currentUser.Role.IsStaff() + && currentUser.Department!.Id != document.Department!.Id) + || (currentUser.Role.IsEmployee() + && document.ImporterId != currentUser.Id); } } \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllDocumentLogsPaginated.cs b/src/Application/Documents/Queries/GetAllDocumentLogsPaginated.cs new file mode 100644 index 00000000..db36ee66 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllDocumentLogsPaginated.cs @@ -0,0 +1,62 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllDocumentLogsPaginated +{ + public record Query : IRequest> + { + public Guid? DocumentId { get; set; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var logs = _context.DocumentLogs + .Include(x => x.ObjectId) + .Include(x => x.User) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.DocumentId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.DocumentId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.ToLower().Contains(request.SearchTerm.ToLower())); + } + + + return await logs + .LoggingListPaginateAsync( + request.Page, + request.Size, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllDocumentsForEmployeePaginated.cs b/src/Application/Documents/Queries/GetAllDocumentsForEmployeePaginated.cs new file mode 100644 index 00000000..ef5a6991 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllDocumentsForEmployeePaginated.cs @@ -0,0 +1,102 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllDocumentsForEmployeePaginated +{ + public record Query : IRequest> + { + public Guid CurrentUserId { get; init; } + public Guid CurrentUserDepartmentId { get; init; } + public Guid? UserId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + public string? DocumentStatus { get; init; } + public bool IsPrivate { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, + CancellationToken cancellationToken) + { + var documents = _context.Documents.AsQueryable(); + + documents = documents + .Include(x => x.Department) + .Include(x => x.Folder) + .ThenInclude(y => y.Locker) + .ThenInclude(z => z.Room); + + if (request.IsPrivate) + { + var permissions = _context.Permissions.Where(x => + x!.EmployeeId == request.CurrentUserId + && x.Document.Department!.Id == request.CurrentUserDepartmentId + && x.AllowedOperations.Contains(DocumentOperation.Read.ToString())) + .Select(x => x.DocumentId); + + documents = documents.Where(x => + x.Department!.Id == request.CurrentUserDepartmentId + && x.IsPrivate + && (permissions.Contains(x.Id) || x.ImporterId == request.CurrentUserId)); + } + else + { + documents = documents.Where(x => + x.Department!.Id == request.CurrentUserDepartmentId + && !x.IsPrivate); + } + + if (request.UserId is not null) + { + documents = documents.Where(x => x.Importer!.Id == request.UserId); + } + + if (request.DocumentStatus is not null + && Enum.TryParse(request.DocumentStatus, true, out DocumentStatus status)) + { + documents = documents.Where(x => x.Status == status); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + documents = documents.Where(x => + x.Title.ToLower().Contains(request.SearchTerm.ToLower())); + } + + return await documents + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs b/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs index fd5c6a0b..40ae1e6d 100644 --- a/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs +++ b/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs @@ -6,6 +6,9 @@ using Application.Common.Models.Dtos.Physical; using AutoMapper; using AutoMapper.QueryableExtensions; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; @@ -29,6 +32,9 @@ public Validator() public record Query : IRequest> { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } + public Guid? UserId { get; init; } public Guid? RoomId { get; init; } public Guid? LockerId { get; init; } public Guid? FolderId { get; init; } @@ -37,6 +43,9 @@ public record Query : IRequest> public int? Size { get; init; } public string? SortBy { get; init; } public string? SortOrder { get; init; } + public string? DocumentStatus { get; init; } + public string? Role { get; init; } + public bool? IsPrivate { get; init; } } public class QueryHandler : IRequestHandler> @@ -50,9 +59,21 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) _mapper = mapper; } - public async Task> Handle(Query request, - CancellationToken cancellationToken) + public async Task> Handle(Query request, CancellationToken cancellationToken) { + if (request.CurrentUser.Role.IsStaff()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (request.RoomId != request.CurrentStaffRoomId) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + var documents = _context.Documents.AsQueryable(); var roomExists = request.RoomId is not null; var lockerExists = request.LockerId is not null; @@ -61,9 +82,29 @@ public async Task> Handle(Query request, documents = documents .Include(x => x.Department) .Include(x => x.Folder) - .ThenInclude(y => y.Locker) - .ThenInclude(z => z.Room) - .ThenInclude(t => t.Department); + .ThenInclude(y => y!.Locker) + .ThenInclude(z => z.Room); + + if (request.DocumentStatus is not null + && Enum.TryParse(request.DocumentStatus, true, out DocumentStatus status)) + { + documents = documents.Where(x => x.Status == status); + } + + if (request.IsPrivate is not null) + { + documents = documents.Where(x => x.IsPrivate == request.IsPrivate); + } + + if (request.UserId is not null) + { + documents = documents.Where(x => x.Importer!.Id == request.UserId); + } + + if (request.Role is not null) + { + documents = documents.Where(x => x.Importer!.Role.ToLower().Equals(request.Role.Trim().ToLower())); + } if (folderExists) { @@ -123,24 +164,14 @@ public async Task> Handle(Query request, x.Title.ToLower().Contains(request.SearchTerm.ToLower())); } - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) - { - sortBy = nameof(DocumentDto.Id); - } - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; - - var count = await documents.CountAsync(cancellationToken); - var list = await documents - .OrderByCustom(sortBy, sortOrder) - .Paginate(pageNumber.Value, sizeNumber.Value) - .ToListAsync(cancellationToken); - - var result = _mapper.Map>(list); - - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + return await documents + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); } } } \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs b/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs new file mode 100644 index 00000000..207309c8 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs @@ -0,0 +1,64 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.ImportDocument; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllIssuedDocumentsPaginated +{ + public record Query : IRequest> + { + public Guid DepartmentId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, + CancellationToken cancellationToken) + { + var documents = _context.Documents + .Include(x => x.Importer) + .Where(x => x.Status == DocumentStatus.Issued); + + documents = documents.Where(x => x.Department!.Id == request.DepartmentId); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + documents = documents.Where(x => + x.Title.ToLower().Contains(request.SearchTerm.ToLower())); + } + + return await documents + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentById.cs b/src/Application/Documents/Queries/GetDocumentById.cs index bf7c29d7..cfaa653d 100644 --- a/src/Application/Documents/Queries/GetDocumentById.cs +++ b/src/Application/Documents/Queries/GetDocumentById.cs @@ -1,6 +1,12 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; +using Application.Identity; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -10,35 +16,57 @@ public class GetDocumentById { public record Query : IRequest { - public Guid DocumentId { get; init; } + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } } public class QueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IPermissionManager _permissionManager; - public QueryHandler(IApplicationDbContext context, IMapper mapper) + public QueryHandler( + IApplicationDbContext context, + IMapper mapper, + IPermissionManager permissionManager) { _context = context; _mapper = mapper; + _permissionManager = permissionManager; } + public async Task Handle(Query request, CancellationToken cancellationToken) { var document = await _context.Documents .Include(x => x.Department) .Include(x => x.Importer) - .Include(x => x.Folder) - .ThenInclude(y => y.Locker) - .ThenInclude(z => z.Room) .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); - + if (document is null) { throw new KeyNotFoundException("Document does not exist."); } - + + if (ViolateConstraints(request.CurrentUser, document)) + { + throw new UnauthorizedAccessException("You don't have permission to view this document."); + } + return _mapper.Map(document); } + + private bool ViolateConstraints(User user, Document document) + => IsStaffAndNotInSameDepartment(user, document) + || IsEmployeeAndDoesNotHasReadPermission(user, document); + + private static bool IsStaffAndNotInSameDepartment(User user, Document document) + => user.Role.IsStaff() + && user.Department!.Id != document.Department!.Id; + + private bool IsEmployeeAndDoesNotHasReadPermission(User user, Document document) + => user.Role.IsEmployee() + && document.ImporterId != user.Id + && !_permissionManager.IsGranted(document.Id, DocumentOperation.Read, user.Id); } } \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentsOfUserPaginated.cs b/src/Application/Documents/Queries/GetDocumentsOfUserPaginated.cs new file mode 100644 index 00000000..d77dbf8d --- /dev/null +++ b/src/Application/Documents/Queries/GetDocumentsOfUserPaginated.cs @@ -0,0 +1,61 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetDocumentsOfUserPaginated +{ + public record Query : IRequest> + { + public Guid UserId { get; set; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + } + + public class Handler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public Handler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.UserId + && x.IsActive + && x.IsActivated, cancellationToken); + + if (user is null) + { + throw new KeyNotFoundException("User does not exist."); + } + + var documents = _context.Documents + .Include(x => x.Department) + .Include(x => x.Folder) + .AsQueryable() + .Where(x => x.Importer!.Id.Equals(request.UserId) && !x.IsPrivate); + + return await documents + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetPermissions.cs b/src/Application/Documents/Queries/GetPermissions.cs new file mode 100644 index 00000000..7413e097 --- /dev/null +++ b/src/Application/Documents/Queries/GetPermissions.cs @@ -0,0 +1,84 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetPermissions +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var document = await GetDocumentWithImporter(request.DocumentId, cancellationToken); + if (document is null) + { + throw new ConflictException("Document does not exist."); + } + + if (IsOwner(request.CurrentUser.Id, document)) + { + return CreatePermissionDto(document.Id, request.CurrentUser.Id, true, true); + } + + var permission = await GetPermission(request.DocumentId, request.CurrentUser.Id, cancellationToken); + + if (permission is null) + { + return CreatePermissionDto(document.Id, request.CurrentUser.Id, false, false); + } + + return _mapper.Map(permission); + } + + private async Task GetDocumentWithImporter(Guid documentId, CancellationToken cancellationToken) + { + return await _context.Documents + .Include(x => x.Importer) + .FirstOrDefaultAsync(x => x.Id == documentId, cancellationToken); + } + + private static bool IsOwner(Guid userId, Document document) + { + return document.Importer!.Id == userId; + } + + private async Task GetPermission(Guid documentId, Guid employeeId, CancellationToken cancellationToken) + { + return await _context.Permissions.FirstOrDefaultAsync( + x => x!.DocumentId == documentId && x.EmployeeId == employeeId, + cancellationToken); + } + + private static PermissionDto CreatePermissionDto(Guid documentId, Guid employeeId, bool canRead, bool canBorrow) + { + return new PermissionDto() + { + DocumentId = documentId, + EmployeeId = employeeId, + CanRead = canRead, + CanBorrow = canBorrow, + }; + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetSelfDocumentsPaginated.cs b/src/Application/Documents/Queries/GetSelfDocumentsPaginated.cs new file mode 100644 index 00000000..7c4d67cb --- /dev/null +++ b/src/Application/Documents/Queries/GetSelfDocumentsPaginated.cs @@ -0,0 +1,59 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetSelfDocumentsPaginated +{ + public record Query : IRequest> + { + public Guid EmployeeId { get; init; } + public string? SearchTerm { get; set; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var documents = _context.Documents.AsQueryable(); + + documents = documents + .Include(x => x.Department) + .Where(x => x.Importer!.Id.Equals(request.EmployeeId)); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + documents = documents.Where(x => + x.Title.ToLower().Contains(request.SearchTerm.ToLower())); + } + + return await documents + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/src/Application/Folders/Commands/AddFolder.cs b/src/Application/Folders/Commands/AddFolder.cs index 4b7bd9b9..9eff4671 100644 --- a/src/Application/Folders/Commands/AddFolder.cs +++ b/src/Application/Folders/Commands/AddFolder.cs @@ -1,12 +1,17 @@ using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Domain.Exceptions; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Folders.Commands; @@ -36,6 +41,8 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public string Name { get; init; } = null!; public string? Description { get; init; } public int Capacity { get; init; } @@ -46,16 +53,20 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { - var locker = await _context.Lockers.FirstOrDefaultAsync(l => l.Id == request.LockerId, cancellationToken); + var locker = await _context.Lockers + .Include(x => x.Room) + .FirstOrDefaultAsync(l => l.Id == request.LockerId, cancellationToken); if (locker is null) { @@ -67,15 +78,20 @@ public async Task Handle(Command request, CancellationToken cancellat throw new LimitExceededException("This locker cannot accept more folders."); } - var folder = await _context.Folders.FirstOrDefaultAsync(x => - x.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()) - && x.Locker.Id.Equals(request.LockerId), cancellationToken); + if (request.CurrentUser.Role.IsStaff() + && (locker.Room.Id != request.CurrentStaffRoomId + || !LockerIsInRoom(locker, request.CurrentStaffRoomId))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } - if (folder is not null) + if (await DuplicatedNameFolderExistsInSameLockerAsync(request.Name, locker.Id, cancellationToken)) { throw new ConflictException("Folder name already exists."); } - + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var entity = new Folder { Name = request.Name.Trim(), @@ -83,13 +99,36 @@ public async Task Handle(Command request, CancellationToken cancellat NumberOfDocuments = 0, Capacity = request.Capacity, Locker = locker, - IsAvailable = true + IsAvailable = true, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var log = new FolderLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = entity.Id, + Time = localDateTimeNow, + Action = FolderLogMessage.Add, }; var result = await _context.Folders.AddAsync(entity, cancellationToken); locker.NumberOfFolders += 1; _context.Lockers.Update(locker); + await _context.FolderLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private async Task DuplicatedNameFolderExistsInSameLockerAsync(string folderName, Guid lockerId, CancellationToken cancellationToken) + { + var folder = await _context.Folders.FirstOrDefaultAsync( + x => x.Name.ToLower().Equals(folderName.ToLower()) + && x.Locker.Id == lockerId, cancellationToken); + return folder is not null; + } + + private static bool LockerIsInRoom(Locker locker, Guid? roomId) + => roomId is not null && locker.Room.Id == roomId; } } \ No newline at end of file diff --git a/src/Application/Folders/Commands/DisableFolder.cs b/src/Application/Folders/Commands/DisableFolder.cs deleted file mode 100644 index f57ab34b..00000000 --- a/src/Application/Folders/Commands/DisableFolder.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Folders.Commands; - -public class DisableFolder -{ - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(f => f.FolderId) - .NotEmpty().WithMessage("FolderId is required."); - } - } - - public record Command : IRequest - { - public Guid FolderId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var folder = await _context.Folders - .Include(x => x.Locker) - .ThenInclude(x => x.Room) - .ThenInclude(x => x.Department) - .FirstOrDefaultAsync(f => f.Id.Equals(request.FolderId), cancellationToken); - - if (folder is null) - { - throw new KeyNotFoundException("Folder does not exist."); - } - - if (!folder.IsAvailable) - { - throw new ConflictException("Folder has already been disabled."); - } - - if (folder.NumberOfDocuments > 0) - { - throw new InvalidOperationException("Folder cannot be disabled because it contains documents."); - } - - folder.IsAvailable = false; - _context.Folders.Update(folder); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(folder); - } - } -} \ No newline at end of file diff --git a/src/Application/Folders/Commands/EnableFolder.cs b/src/Application/Folders/Commands/EnableFolder.cs deleted file mode 100644 index e7acf703..00000000 --- a/src/Application/Folders/Commands/EnableFolder.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Folders.Commands; - -public class EnableFolder -{ - public record Command : IRequest - { - public Guid FolderId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var folder = await _context.Folders - .Include(x => x.Locker) - .ThenInclude(x => x.Room) - .ThenInclude(x => x.Department) - .FirstOrDefaultAsync(x => x.Id.Equals(request.FolderId), cancellationToken); - - if (folder is null) - { - throw new KeyNotFoundException("Folder does not exist."); - } - - if (folder.IsAvailable) - { - throw new ConflictException("Folder has already been enabled."); - } - - folder.IsAvailable = true; - var result = _context.Folders.Update(folder); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Folders/Commands/RemoveFolder.cs b/src/Application/Folders/Commands/RemoveFolder.cs index e8bc05f2..c6a0c6c7 100644 --- a/src/Application/Folders/Commands/RemoveFolder.cs +++ b/src/Application/Folders/Commands/RemoveFolder.cs @@ -1,11 +1,15 @@ using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; -using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Folders.Commands; @@ -13,6 +17,8 @@ public class RemoveFolder { public record Command : IRequest { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public Guid FolderId { get; init; } } @@ -20,11 +26,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -40,19 +48,39 @@ public async Task Handle(Command request, CancellationToken cancellat throw new KeyNotFoundException("Folder does not exist."); } - var containDocument = folder.NumberOfDocuments > 0; + if (request.CurrentUser.Role.IsStaff() + && (request.CurrentStaffRoomId is null || !FolderIsInRoom(folder, request.CurrentStaffRoomId.Value))) + { + throw new UnauthorizedAccessException("User cannot remove this resource."); + } - if (containDocument) + var canNotRemove = folder.NumberOfDocuments > 0; + + if (canNotRemove) { throw new ConflictException("Folder cannot be removed because it contains documents."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var locker = folder.Locker; + + var log = new FolderLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = folder.Id, + Time = localDateTimeNow, + Action = FolderLogMessage.Remove, + }; var result = _context.Folders.Remove(folder); locker.NumberOfFolders -= 1; - + await _context.FolderLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private static bool FolderIsInRoom(Folder folder, Guid roomId) + => folder.Locker.Room.Id == roomId; } } \ No newline at end of file diff --git a/src/Application/Folders/Commands/UpdateFolder.cs b/src/Application/Folders/Commands/UpdateFolder.cs index edfcfe76..cec1d243 100644 --- a/src/Application/Folders/Commands/UpdateFolder.cs +++ b/src/Application/Folders/Commands/UpdateFolder.cs @@ -1,10 +1,16 @@ using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Folders.Commands; @@ -31,6 +37,8 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public Guid FolderId { get; init; } public string Name { get; init; } = null!; public string? Description { get; init; } @@ -41,11 +49,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -60,14 +70,14 @@ public async Task Handle(Command request, CancellationToken cancellat { throw new KeyNotFoundException("Folder does not exist."); } - - var nameExisted = await _context.Folders.AnyAsync( x => - x.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()) - && x.Id != folder.Id - && x.Locker.Id == folder.Locker.Id - , cancellationToken); - - if (nameExisted) + + if (request.CurrentUser.Role.IsStaff() + && (request.CurrentStaffRoomId is null || !FolderIsInRoom(folder, request.CurrentStaffRoomId!.Value))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (await DuplicatedNameFolderExistsInSameLockerAsync(request.Name, folder.Id, folder.Locker.Id, cancellationToken)) { throw new ConflictException("Folder name already exists."); } @@ -77,13 +87,42 @@ public async Task Handle(Command request, CancellationToken cancellat throw new ConflictException("New capacity cannot be less than current number of documents."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + folder.Name = request.Name; folder.Description = request.Description; folder.Capacity = request.Capacity; - + folder.LastModified = localDateTimeNow; + folder.LastModifiedBy = request.CurrentUser.Id; + + var log = new FolderLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = folder.Id, + Time = localDateTimeNow, + Action = FolderLogMessage.Update, + }; var result = _context.Folders.Update(folder); + await _context.FolderLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + private async Task DuplicatedNameFolderExistsInSameLockerAsync( + string folderName, + Guid lockerId, + Guid folderId, + CancellationToken cancellationToken) + { + var folder = await _context.Folders.FirstOrDefaultAsync( + x => x.Name.Trim().ToLower().Equals(folderName.Trim().ToLower()) + && x.Id != folderId + && x.Locker.Id == lockerId, + cancellationToken); + return folder is not null; + } + + private static bool FolderIsInRoom(Folder folder, Guid roomId) + => folder.Locker.Room.Id == roomId; } } \ No newline at end of file diff --git a/src/Application/Folders/Queries/GetAllFolderLogsPaginated.cs b/src/Application/Folders/Queries/GetAllFolderLogsPaginated.cs new file mode 100644 index 00000000..537c2dc0 --- /dev/null +++ b/src/Application/Folders/Queries/GetAllFolderLogsPaginated.cs @@ -0,0 +1,60 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Folders.Queries; + +public class GetAllFolderLogsPaginated +{ + public record Query : IRequest> + { + public Guid? FolderId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var logs = _context.FolderLogs + .Include(x => x.ObjectId) + .AsQueryable(); + + if (request.FolderId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.FolderId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.Trim().ToLower().Contains(request.SearchTerm.Trim().ToLower())); + } + + + return await logs + .LoggingListPaginateAsync( + request.Page, + request.Size, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Folders/Queries/GetAllFoldersPaginated.cs b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs index 530d039f..d2d9d77d 100644 --- a/src/Application/Folders/Queries/GetAllFoldersPaginated.cs +++ b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs @@ -28,6 +28,8 @@ public Validator() public record Query : IRequest> { + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public Guid? RoomId { get; init; } public Guid? LockerId { get; init; } public string? SearchTerm { get; init; } @@ -50,15 +52,22 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task> Handle(Query request, CancellationToken cancellationToken) { + if (request.CurrentUserRole.IsStaff() + && (request.CurrentStaffRoomId is null || request.RoomId is null + || !IsSameRoom(request.RoomId.Value, request.CurrentStaffRoomId.Value))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + var folders = _context.Folders .Include(x => x.Locker) .ThenInclude(y => y.Room) .ThenInclude(z => z.Department) .AsQueryable(); - var roomExists = request.RoomId is not null; - var lockerExists = request.LockerId is not null; + var roomIdProvided = request.RoomId is not null; + var lockerIdProvided = request.LockerId is not null; - if (lockerExists) + if (lockerIdProvided) { var locker = await _context.Lockers .Include(x => x.Room) @@ -76,7 +85,7 @@ public async Task> Handle(Query request, CancellationTo folders = folders.Where(x => x.Locker.Id == request.LockerId); } - else if (roomExists) + else if (roomIdProvided) { var room = await _context.Rooms .FirstOrDefaultAsync(x => x.Id == request.RoomId @@ -95,24 +104,17 @@ public async Task> Handle(Query request, CancellationTo x.Name.ToLower().Contains(request.SearchTerm.ToLower())); } - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) - { - sortBy = nameof(LockerDto.Id); - } - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; - - var count = await folders.CountAsync(cancellationToken); - var list = await folders - .OrderByCustom(sortBy, sortOrder) - .Paginate(pageNumber.Value, sizeNumber.Value) - .ToListAsync(cancellationToken); - - var result = _mapper.Map>(list); - - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + return await folders + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); } + + private static bool IsSameRoom(Guid roomId1, Guid roomId2) + => roomId1 == roomId2; } } \ No newline at end of file diff --git a/src/Application/Folders/Queries/GetFolderById.cs b/src/Application/Folders/Queries/GetFolderById.cs index c74c30c8..a2183e56 100644 --- a/src/Application/Folders/Queries/GetFolderById.cs +++ b/src/Application/Folders/Queries/GetFolderById.cs @@ -1,6 +1,8 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -10,6 +12,8 @@ public class GetFolderById { public record Query : IRequest { + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public Guid FolderId { get; init; } } @@ -36,8 +40,17 @@ public async Task Handle(Query request, CancellationToken cancellatio { throw new KeyNotFoundException("Folder does not exist."); } + + if (request.CurrentUserRole.IsStaff() + && (request.CurrentStaffRoomId is null || !FolderInSameRoom(folder, request.CurrentStaffRoomId.Value))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } return _mapper.Map(folder); } + + private static bool FolderInSameRoom(Folder folder, Guid roomId) + => folder.Locker.Room.Id == roomId; } } \ No newline at end of file diff --git a/src/Application/ImportRequests/Commands/ApproveOrRejectDocument.cs b/src/Application/ImportRequests/Commands/ApproveOrRejectDocument.cs new file mode 100644 index 00000000..e428ad20 --- /dev/null +++ b/src/Application/ImportRequests/Commands/ApproveOrRejectDocument.cs @@ -0,0 +1,137 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.ImportRequests.Commands; + +public class ApproveOrRejectDocument +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Decision) + .Must(x => x.IsApproval() || x.IsRejection()).WithMessage("Decision is not valid."); + } + } + + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid ImportRequestId { get; init; } + public string Decision { get; init; } = null!; + public string StaffReason { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var importRequest = await _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.ImportRequestId + && x.Status == ImportRequestStatus.Pending, cancellationToken); + + if (importRequest is null) + { + throw new KeyNotFoundException("Import request does not exist."); + } + + var document = await _context.Documents + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == importRequest.Document.Id + && x.Status == DocumentStatus.Issued, cancellationToken); + if (document is null) + { + throw new ConflictException("Document does not exist."); + } + + var staff = await _context.Staffs + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.CurrentUser.Id, cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + if (staff.Room is null) + { + throw new ConflictException("Staff does not assign to a room."); + } + + if (staff.Room.Id != importRequest.RoomId) + { + throw new KeyNotFoundException("Can not approve request from different room"); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var log = new DocumentLog() + { + ObjectId = document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = DocumentLogMessages.Import.Approve, + }; + + var requestLog = new RequestLog() + { + ObjectId = document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = RequestLogMessages.ApproveImport, + }; + + if (request.Decision.IsApproval()) + { + importRequest.Status = ImportRequestStatus.Approved; + log.Action = DocumentLogMessages.Import.Approve; + requestLog.Action = RequestLogMessages.ApproveImport; + } + + if (request.Decision.IsRejection()) + { + importRequest.Status = ImportRequestStatus.Rejected; + log.Action = DocumentLogMessages.Import.Reject; + requestLog.Action = RequestLogMessages.RejectImport; + } + + importRequest.StaffReason = request.StaffReason; + importRequest.LastModified = localDateTimeNow; + importRequest.LastModifiedBy = request.CurrentUser.Id; + + var result = _context.ImportRequests.Update(importRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.RequestLogs.AddAsync(requestLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/ImportRequests/Commands/AssignDocument.cs b/src/Application/ImportRequests/Commands/AssignDocument.cs new file mode 100644 index 00000000..8b8f1fb0 --- /dev/null +++ b/src/Application/ImportRequests/Commands/AssignDocument.cs @@ -0,0 +1,104 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.ImportRequests.Commands; + +public class AssignDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid? StaffRoomId { get; init; } + public Guid ImportRequestId { get; init; } + public Guid FolderId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + + var importRequest = await _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.ImportRequestId, cancellationToken); + + if (importRequest is null) + { + throw new KeyNotFoundException("Import request does not exist."); + } + + if (request.StaffRoomId is null || importRequest.RoomId != request.StaffRoomId.Value) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (importRequest.Status is not ImportRequestStatus.Approved) + { + throw new ConflictException("Request cannot be assigned."); + } + + var folder = await _context.Folders + .FirstOrDefaultAsync(x => x.Id == request.FolderId + && x.Locker.Room.Id == request.StaffRoomId, cancellationToken); + + if (folder is null) + { + throw new ConflictException("Folder does not exist."); + } + + if (folder.NumberOfDocuments >= folder.Capacity) + { + throw new ConflictException("This folder cannot accept more documents."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + importRequest.Document.Folder = folder; + importRequest.Document.LastModified = localDateTimeNow; + importRequest.Document.LastModifiedBy = request.CurrentUser.Id; + + var log = new DocumentLog() + { + ObjectId = importRequest.Document.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = DocumentLogMessages.Import.Assign, + }; + var folderLog = new FolderLog() + { + ObjectId = folder.Id, + Time = localDateTimeNow, + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + Action = FolderLogMessage.AssignDocument, + }; + _context.Documents.Update(importRequest.Document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.FolderLogs.AddAsync(folderLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(importRequest); + } + } +} \ No newline at end of file diff --git a/src/Application/ImportRequests/Commands/CheckinDocument.cs b/src/Application/ImportRequests/Commands/CheckinDocument.cs new file mode 100644 index 00000000..70c0c34a --- /dev/null +++ b/src/Application/ImportRequests/Commands/CheckinDocument.cs @@ -0,0 +1,104 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.ImportRequests.Commands; + +public class CheckinDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Department) + .Include(x => x.Folder) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + var importRequest = await _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.DocumentId == request.DocumentId, cancellationToken); + + if (importRequest is null) + { + throw new ConflictException("This document does not have an import request."); + } + + if (StatusesAreNotValid(document.Status, importRequest.Status)) + { + throw new ConflictException("Request cannot be checked in."); + } + + if (document.Folder is null) + { + throw new ConflictException("Request cannot be checked in."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + document.Status = DocumentStatus.Available; + document.LastModified = localDateTimeNow; + document.LastModifiedBy = request.CurrentUser.Id; + importRequest.Status = ImportRequestStatus.CheckedIn; + importRequest.LastModified = localDateTimeNow; + importRequest.LastModifiedBy = request.CurrentUser.Id; + + var log = new DocumentLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = document.Id, + Time = localDateTimeNow, + Action = DocumentLogMessages.Import.Checkin, + }; + var importLog = new RequestLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = document.Id, + Time = localDateTimeNow, + Action = RequestLogMessages.CheckInImport, + }; + var result = _context.Documents.Update(document); + _context.ImportRequests.Update(importRequest); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.RequestLogs.AddAsync(importLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + + private static bool StatusesAreNotValid(DocumentStatus documentStatus, ImportRequestStatus importRequestStatus) + => documentStatus is not DocumentStatus.Issued || importRequestStatus is not ImportRequestStatus.Approved; + } +} \ No newline at end of file diff --git a/src/Application/ImportRequests/Commands/RequestImportDocument.cs b/src/Application/ImportRequests/Commands/RequestImportDocument.cs new file mode 100644 index 00000000..d27cbeef --- /dev/null +++ b/src/Application/ImportRequests/Commands/RequestImportDocument.cs @@ -0,0 +1,101 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.ImportRequests.Commands; + +public class RequestImportDocument +{ + public record Command : IRequest + { + public string Title { get; init; } = null!; + public string? Description { get; init; } + public string DocumentType { get; init; } = null!; + public string ImportReason { get; init; } = null!; + public User Issuer { get; init; } = null!; + public Guid RoomId { get; init; } + public bool IsPrivate { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = _context.Documents.FirstOrDefault(x => + x.Title.Trim().ToLower().Equals(request.Title.Trim().ToLower()) + && x.Importer!.Id == request.Issuer.Id); + if (document is not null) + { + throw new ConflictException($"Document title already exists for user {request.Issuer.FirstName}."); + } + + var room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id == request.RoomId && x.IsAvailable, cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var entity = new Document() + { + Title = request.Title.Trim(), + Description = request.Description?.Trim(), + DocumentType = request.DocumentType.Trim(), + Importer = request.Issuer, + Department = request.Issuer.Department, + Status = DocumentStatus.Issued, + IsPrivate = request.IsPrivate, + Created = localDateTimeNow, + CreatedBy = request.Issuer.Id, + }; + await _context.Documents.AddAsync(entity, cancellationToken); + + var importRequest = new ImportRequest() + { + Document = entity, + Status = ImportRequestStatus.Pending, + Room = room, + Created = localDateTimeNow, + CreatedBy = request.Issuer.Id, + ImportReason = request.ImportReason, + StaffReason = string.Empty, + }; + + var log = new DocumentLog() + { + ObjectId = entity.Id, + Time = localDateTimeNow, + User = request.Issuer, + UserId = request.Issuer.Id, + Action = DocumentLogMessages.Import.NewImportRequest, + }; + var result = await _context.ImportRequests.AddAsync(importRequest, cancellationToken); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/ImportRequests/Queries/GetAllImportRequestsPaginated.cs b/src/Application/ImportRequests/Queries/GetAllImportRequestsPaginated.cs new file mode 100644 index 00000000..f90d4991 --- /dev/null +++ b/src/Application/ImportRequests/Queries/GetAllImportRequestsPaginated.cs @@ -0,0 +1,106 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.ImportRequests.Queries; + +public class GetAllImportRequestsPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public Guid? RoomId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + if (request.CurrentUser.Role.IsStaff()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + + var room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + var roomDoesNotExist = room is null; + + if (roomDoesNotExist + || RoomIsNotInSameDepartment(request.CurrentUser, room!)) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + } + + if (request.CurrentUser.Role.IsEmployee()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + + var room = await _context.Rooms + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + var roomDoesNotExist = room is null; + + if (roomDoesNotExist + || RoomIsNotInSameDepartment(request.CurrentUser, room!)) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + } + + var importRequests = _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.RoomId is not null) + { + importRequests = importRequests.Where(x => x.RoomId == request.RoomId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + importRequests = importRequests.Where(x => + x.Document.Title.ToLower().Contains(request.SearchTerm.ToLower())); + } + + + return await importRequests + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + + private static bool RoomIsNotInSameDepartment(User user, Room room) + => user.Department?.Id != room.DepartmentId; + } +} \ No newline at end of file diff --git a/src/Application/ImportRequests/Queries/GetImportRequestById.cs b/src/Application/ImportRequests/Queries/GetImportRequestById.cs new file mode 100644 index 00000000..625400cd --- /dev/null +++ b/src/Application/ImportRequests/Queries/GetImportRequestById.cs @@ -0,0 +1,57 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.ImportRequests.Queries; + +public class GetImportRequestById { + public record Query : IRequest + { + public Guid CurrentUserId { get; init; } + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } + public Guid RequestId { get; init; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var importRequest = await _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id.Equals(request.RequestId), cancellationToken); + + if (importRequest is null) + { + throw new KeyNotFoundException("Import request does not exist."); + } + + if (request.CurrentUserRole.IsStaff() + && (request.CurrentStaffRoomId is null || importRequest.Room.Id != request.CurrentStaffRoomId)) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (request.CurrentUserRole.IsEmployee() + && importRequest.Document.ImporterId != request.CurrentUserId) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + return _mapper.Map(importRequest); + } + } +} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/AddLocker.cs b/src/Application/Lockers/Commands/AddLocker.cs index 78cbb371..f1a9097c 100644 --- a/src/Application/Lockers/Commands/AddLocker.cs +++ b/src/Application/Lockers/Commands/AddLocker.cs @@ -1,12 +1,16 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Domain.Exceptions; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Lockers.Commands; @@ -35,6 +39,7 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public string Name { get; init; } = null!; public string? Description { get; init; } public Guid RoomId { get; init; } @@ -45,11 +50,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -63,34 +70,50 @@ public async Task Handle(Command request, CancellationToken cancellat if (room.NumberOfLockers >= room.Capacity) { - throw new LimitExceededException( - "This room cannot accept more lockers." - ); + throw new LimitExceededException("This room cannot accept more lockers."); } - var locker = await _context.Lockers.FirstOrDefaultAsync( - x => x.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()) && x.Room.Id.Equals(request.RoomId), - cancellationToken); - if (locker is not null) + if (await DuplicatedNameLockerExistsInSameRoomAsync(request.Name, request.RoomId, cancellationToken)) { throw new ConflictException("Locker name already exists."); } - var entity = new Locker + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var entity = new Locker() { Name = request.Name.Trim(), Description = request.Description?.Trim(), NumberOfFolders = 0, Capacity = request.Capacity, Room = room, - IsAvailable = true + IsAvailable = true, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var log = new LockerLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = entity.Id, + Time = localDateTimeNow, + Action = LockerLogMessage.Add, }; - var result = await _context.Lockers.AddAsync(entity, cancellationToken); room.NumberOfLockers += 1; _context.Rooms.Update(room); + await _context.LockerLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private async Task DuplicatedNameLockerExistsInSameRoomAsync(string lockerName, Guid roomId, CancellationToken cancellationToken) + { + var locker = await _context.Lockers.FirstOrDefaultAsync( + x => x.Name.ToLower().Equals(lockerName.ToLower()) + && x.Room.Id == roomId, cancellationToken); + return locker is not null; + } } } \ No newline at end of file diff --git a/src/Application/Lockers/Commands/DisableLocker.cs b/src/Application/Lockers/Commands/DisableLocker.cs deleted file mode 100644 index 10a78f79..00000000 --- a/src/Application/Lockers/Commands/DisableLocker.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Lockers.Commands; - -public class DisableLocker -{ - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.LockerId) - .NotEmpty().WithMessage("LockerId is required."); - } - } - - public record Command : IRequest - { - public Guid LockerId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var locker = await _context.Lockers - .Include(x => x.Room) - .ThenInclude(x => x.Department) - .FirstOrDefaultAsync(x => x.Id.Equals(request.LockerId), cancellationToken); - - if (locker is null) - { - throw new KeyNotFoundException("Locker does not exist."); - } - - if (!locker.IsAvailable) - { - throw new ConflictException("Locker has already been disabled."); - } - - var canNotDisable = await _context.Documents - .CountAsync(x => x.Folder!.Locker.Id.Equals(request.LockerId), cancellationToken) - > 0; - - if (canNotDisable) - { - throw new InvalidOperationException("Locker cannot be disabled because it contains documents."); - } - - var folders = _context.Folders.Where(x => x.Locker.Room.Id.Equals(locker.Id)); - - foreach (var folder in folders) - { - folder.IsAvailable = false; - } - _context.Folders.UpdateRange(folders); - - locker.IsAvailable = false; - var result = _context.Lockers.Update(locker); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/EnableLocker.cs b/src/Application/Lockers/Commands/EnableLocker.cs deleted file mode 100644 index 49722a1a..00000000 --- a/src/Application/Lockers/Commands/EnableLocker.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Lockers.Commands; - -public class EnableLocker -{ - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.LockerId) - .NotEmpty().WithMessage("LockerId is required."); - } - } - - public record Command : IRequest - { - public Guid LockerId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var locker = await _context.Lockers - .Include(x => x.Room) - .ThenInclude(x => x.Department) - .FirstOrDefaultAsync(x => x.Id.Equals(request.LockerId), cancellationToken); - if (locker is null) - { - throw new KeyNotFoundException("Locker does not exist."); - } - - if (locker.IsAvailable) - { - throw new ConflictException("Locker has already been enabled."); - } - - locker.IsAvailable = true; - var result = _context.Lockers.Update(locker); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/RemoveLocker.cs b/src/Application/Lockers/Commands/RemoveLocker.cs index a6ab6ad9..c58f35ab 100644 --- a/src/Application/Lockers/Commands/RemoveLocker.cs +++ b/src/Application/Lockers/Commands/RemoveLocker.cs @@ -1,10 +1,14 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Lockers.Commands; @@ -23,6 +27,7 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid LockerId { get; init; } } @@ -30,11 +35,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -43,26 +50,34 @@ public async Task Handle(Command request, CancellationToken cancellat .Include(x => x.Room) .ThenInclude(x => x.Department) .FirstOrDefaultAsync(x => x.Id.Equals(request.LockerId), cancellationToken); - + if (locker is null) { throw new KeyNotFoundException("Locker does not exist."); } - + var canNotRemove = await _context.Documents - .CountAsync(x => x.Folder!.Locker.Id.Equals(request.LockerId), cancellationToken) - > 0; - + .AnyAsync(x => x.Folder!.Locker.Id.Equals(request.LockerId), cancellationToken); if (canNotRemove) { - throw new InvalidOperationException("Locker cannot be removed because it contains documents."); + throw new ConflictException("Locker cannot be removed because it contains documents."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var log = new LockerLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = locker.Id, + Time = localDateTimeNow, + Action = LockerLogMessage.Remove, + }; var room = locker.Room; - var result = _context.Lockers.Remove(locker); room.NumberOfLockers -= 1; _context.Rooms.Update(room); + await _context.LockerLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Lockers/Commands/UpdateLocker.cs b/src/Application/Lockers/Commands/UpdateLocker.cs index 20383ef9..e618129a 100644 --- a/src/Application/Lockers/Commands/UpdateLocker.cs +++ b/src/Application/Lockers/Commands/UpdateLocker.cs @@ -1,12 +1,16 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Domain.Exceptions; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Lockers.Commands; @@ -31,6 +35,7 @@ public Validator() } public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid LockerId { get; init; } public string Name { get; init; } = null!; public string? Description { get; init; } @@ -41,11 +46,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -54,34 +61,57 @@ public async Task Handle(Command request, CancellationToken cancellat .Include(x => x.Room) .ThenInclude(x => x.Department) .FirstOrDefaultAsync(x => x.Id.Equals(request.LockerId), cancellationToken); - + if (locker is null) { throw new KeyNotFoundException("Locker does not exist."); } - - var duplicateLocker = await _context.Lockers.FirstOrDefaultAsync( - x => x.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()) - && x.Id != locker.Id - && x.Room.Id == locker.Room.Id, cancellationToken); - if (duplicateLocker is not null && !duplicateLocker.Equals(locker)) + if (await DuplicatedNameLockerExistsInSameRoomAsync(request.Name, locker.Room.Id, request.LockerId, cancellationToken)) { throw new ConflictException("New locker name already exists."); } - + if (locker.NumberOfFolders > request.Capacity) { throw new ConflictException("New capacity cannot be less than current number of folders."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + // update work locker.Name = request.Name; locker.Description = request.Description; locker.Capacity = request.Capacity; - + locker.LastModified = localDateTimeNow; + locker.LastModifiedBy = request.CurrentUser.Id; + + var log = new LockerLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = locker.Id, + Time = localDateTimeNow, + Action = LockerLogMessage.Update, + }; var result = _context.Lockers.Update(locker); + await _context.LockerLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private async Task DuplicatedNameLockerExistsInSameRoomAsync( + string lockerName, + Guid roomId, + Guid lockerId, + CancellationToken cancellationToken) + { + var locker = await _context.Lockers.FirstOrDefaultAsync( + x => x.Name.Trim().ToLower().Equals(lockerName.ToLower()) + && x.Id != lockerId + && x.Room.Id == roomId, + cancellationToken); + return locker is not null; + } } } \ No newline at end of file diff --git a/src/Application/Lockers/Queries/GetAllLockerLogsPaginated.cs b/src/Application/Lockers/Queries/GetAllLockerLogsPaginated.cs new file mode 100644 index 00000000..6adb27be --- /dev/null +++ b/src/Application/Lockers/Queries/GetAllLockerLogsPaginated.cs @@ -0,0 +1,93 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Lockers.Queries; + +public class GetAllLockerLogsPaginated +{ + public record Query : IRequest> + { + public string CurrentUserRole { get; init; } = null!; + public Guid CurrentUserDepartmentId { get; init; } + public string? SearchTerm { get; init; } + public Guid? LockerId { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + + if (request.CurrentUserRole.IsStaff()) + { + if (request.LockerId is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + var currentRoom = await GetRoomByDepartmentIdAsync(request.CurrentUserDepartmentId, cancellationToken); + + if (currentRoom is null) + { + throw new UnauthorizedAccessException("User cannot access this resource"); + } + + if (!IsSameRoom(currentRoom.Id, request.LockerId.Value)) + { + throw new UnauthorizedAccessException("User cannot access this resource"); + } + } + + var logs = _context.LockerLogs + .Include(x => x.ObjectId) + .Include(x => x.User) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.LockerId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.LockerId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.Trim().ToLower().Contains(request.SearchTerm.Trim().ToLower())); + } + + return await logs + .LoggingListPaginateAsync( + request.Page, + request.Size, + _mapper.ConfigurationProvider, + cancellationToken); + } + + private async Task GetRoomByDepartmentIdAsync(Guid departmentId, CancellationToken cancellationToken) + => await _context.Rooms.FirstOrDefaultAsync( + x => x.DepartmentId == departmentId, + cancellationToken); + + private static bool IsSameRoom(Guid roomId1, Guid roomId2) + => roomId1 == roomId2; + } +} diff --git a/src/Application/Lockers/Queries/GetAllLockersPaginated.cs b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs index 3e08e523..ad6b845c 100644 --- a/src/Application/Lockers/Queries/GetAllLockersPaginated.cs +++ b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs @@ -1,3 +1,4 @@ +using Application.Common.Exceptions; using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Mappings; @@ -5,6 +6,7 @@ using Application.Common.Models.Dtos.Physical; using AutoMapper; using AutoMapper.QueryableExtensions; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -14,6 +16,8 @@ public class GetAllLockersPaginated { public record Query : IRequest> { + public string CurrentUserRole { get; init; } = null!; + public Guid CurrentUserDepartmentId { get; init; } public Guid? RoomId { get; init; } public string? SearchTerm { get; init; } public int? Page { get; init; } @@ -35,6 +39,26 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task> Handle(Query request, CancellationToken cancellationToken) { + if (request.CurrentUserRole.IsStaff()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + var currentUserRoom = await GetRoomByDepartmentIdAsync(request.CurrentUserDepartmentId, cancellationToken); + + if (currentUserRoom is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (!IsSameRoom(currentUserRoom.Id, request.RoomId.Value)) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + var lockers = _context.Lockers .Include(x => x.Room) .ThenInclude(y => y.Department) @@ -51,24 +75,22 @@ public async Task> Handle(Query request, CancellationTo x.Name.ToLower().Contains(request.SearchTerm.ToLower())); } - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) - { - sortBy = nameof(LockerDto.Id); - } - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; + return await lockers + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } - var count = await lockers.CountAsync(cancellationToken); - var list = await lockers - .OrderByCustom(sortBy, sortOrder) - .Paginate(pageNumber.Value, sizeNumber.Value) - .ToListAsync(cancellationToken); - - var result = _mapper.Map>(list); + private async Task GetRoomByDepartmentIdAsync(Guid departmentId, CancellationToken cancellationToken) + => await _context.Rooms.FirstOrDefaultAsync( + x => x.DepartmentId == departmentId, + cancellationToken); - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); - } + private static bool IsSameRoom(Guid roomId1, Guid roomId2) + => roomId1 == roomId2; } } \ No newline at end of file diff --git a/src/Application/Lockers/Queries/GetLockerById.cs b/src/Application/Lockers/Queries/GetLockerById.cs index 64a1bf5a..3a21a7a4 100644 --- a/src/Application/Lockers/Queries/GetLockerById.cs +++ b/src/Application/Lockers/Queries/GetLockerById.cs @@ -1,6 +1,9 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; +using Application.Identity; using AutoMapper; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -10,6 +13,8 @@ public class GetLockerById { public record Query : IRequest { + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } public Guid LockerId { get; init; } } @@ -35,8 +40,19 @@ public async Task Handle(Query request, CancellationToken cancellatio { throw new KeyNotFoundException("Locker does not exist."); } - + + if (request.CurrentUserRole.IsStaff() + && (request.CurrentStaffRoomId is null || !LockerInSameRoom(locker, request.CurrentStaffRoomId.Value))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + return _mapper.Map(locker); } + + private static bool LockerInSameRoom( + Locker locker, + Guid roomId) + => locker.Room.Id == roomId; } } \ No newline at end of file diff --git a/src/Application/Rooms/Commands/AddRoom.cs b/src/Application/Rooms/Commands/AddRoom.cs index 13449b93..031b1131 100644 --- a/src/Application/Rooms/Commands/AddRoom.cs +++ b/src/Application/Rooms/Commands/AddRoom.cs @@ -1,11 +1,15 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Rooms.Commands; @@ -41,6 +45,7 @@ private bool BeUnique(string name) public record Command : IRequest { + public User CurrentUser { get; init; } public string Name { get; init; } = null!; public string? Description { get; init; } public int Capacity { get; init; } @@ -51,10 +56,12 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + private readonly IDateTimeProvider _dateTimeProvider; + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -75,6 +82,7 @@ public async Task Handle(Command request, CancellationToken cancellatio throw new ConflictException("Room name already exists."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); var entity = new Room { Name = request.Name.Trim(), @@ -84,8 +92,20 @@ public async Task Handle(Command request, CancellationToken cancellatio Department = department, DepartmentId = request.DepartmentId, IsAvailable = true, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var log = new RoomLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = entity.Id, + Time = localDateTimeNow, + Action = RoomLogMessage.Add, }; var result = await _context.Rooms.AddAsync(entity, cancellationToken); + await _context.RoomLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Rooms/Commands/DisableRoom.cs b/src/Application/Rooms/Commands/DisableRoom.cs deleted file mode 100644 index c70fe9a5..00000000 --- a/src/Application/Rooms/Commands/DisableRoom.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Rooms.Commands; - -public class DisableRoom -{ - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.RoomId) - .NotEmpty().WithMessage("RoomId is required."); - } - } - - public record Command : IRequest - { - public Guid RoomId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var room = await _context.Rooms - .Include(x => x.Department) - .Include(x => x.Staff) - .Include(x => x.Lockers) - .ThenInclude(y => y.Folders) - .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); - - if (room is null) - { - throw new KeyNotFoundException("Room does not exist."); - } - - if (!room.IsAvailable) - { - throw new ConflictException("Room have already been disabled."); - } - - var canNotDisable = await _context.Documents - .CountAsync(x => x.Folder!.Locker.Room.Id.Equals(request.RoomId), cancellationToken) - > 0; - - if (canNotDisable) - { - throw new InvalidOperationException("Room cannot be disabled because it contains documents."); - } - - var lockers = _context.Lockers.Include(x=> x.Folders) - .Where(x => x.Room.Id.Equals(room.Id)); - - foreach (var locker in lockers) - { - foreach (var folder in locker.Folders) - { - folder.IsAvailable = false; - } - locker.IsAvailable = false; - } - _context.Lockers.UpdateRange(lockers); - room.IsAvailable = false; - var result = _context.Rooms.Update(room); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/EnableRoom.cs b/src/Application/Rooms/Commands/EnableRoom.cs deleted file mode 100644 index d27e9faf..00000000 --- a/src/Application/Rooms/Commands/EnableRoom.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Rooms.Commands; - -public class EnableRoom -{ - public class Validator : AbstractValidator - { - public Validator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.RoomId) - .NotEmpty().WithMessage("RoomId is required."); - } - } - public record Command : IRequest - { - public Guid RoomId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var room = await _context.Rooms - .Include(x => x.Department) - .Include(x => x.Staff) - .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); - - if (room is null) - { - throw new KeyNotFoundException("Room does not exist."); - } - - if (room.IsAvailable) - { - throw new ConflictException("Room has already been enabled."); - } - - room.IsAvailable = true; - var result = _context.Rooms.Update(room); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/RemoveRoom.cs b/src/Application/Rooms/Commands/RemoveRoom.cs index b389e8d6..6f56a584 100644 --- a/src/Application/Rooms/Commands/RemoveRoom.cs +++ b/src/Application/Rooms/Commands/RemoveRoom.cs @@ -1,9 +1,14 @@ +using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Rooms.Commands; @@ -22,6 +27,7 @@ public Validator() public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid RoomId { get; init; } } @@ -29,34 +35,49 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { var room = await _context.Rooms .Include(x => x.Department) - .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); - + .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken); + if (room is null) { throw new KeyNotFoundException("Room does not exist."); } - var canNotRemove = await _context.Documents - .CountAsync(x => x.Folder!.Locker.Room.Id.Equals(request.RoomId), cancellationToken: cancellationToken) - > 0; - - if (canNotRemove) + var containsDocuments = await _context.Documents + .AnyAsync(x => x.Folder!.Locker.Room.Id == room.Id, cancellationToken); + var containsFolders = await _context.Folders + .AnyAsync(x => x.Locker.Room.Id == room.Id, cancellationToken); + var containsLockers = await _context.Lockers + .AnyAsync(x => x.Room.Id == room.Id, cancellationToken); + if (containsDocuments || containsFolders || containsLockers) { - throw new InvalidOperationException("Room cannot be removed because it contains documents."); + throw new ConflictException("Room cannot be removed because it contains something."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var log = new RoomLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = room.Id, + Time = localDateTimeNow, + Action = RoomLogMessage.Remove, + }; var result = _context.Rooms.Remove(room); + await _context.RoomLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Rooms/Commands/UpdateRoom.cs b/src/Application/Rooms/Commands/UpdateRoom.cs index a4fbe702..d585ae1d 100644 --- a/src/Application/Rooms/Commands/UpdateRoom.cs +++ b/src/Application/Rooms/Commands/UpdateRoom.cs @@ -1,11 +1,15 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using Domain.Entities.Physical; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Rooms.Commands; @@ -30,21 +34,25 @@ public Validator() } public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid RoomId { get; init; } public string Name { get; init; } = null!; public string? Description { get; init; } public int Capacity { get; init; } + public bool IsAvailable { get; init; } } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -52,6 +60,7 @@ public async Task Handle(Command request, CancellationToken cancellatio var room = await _context.Rooms .Include(x => x.Department) .Include(x => x.Staff) + .AsNoTracking() .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); if (room is null) @@ -59,12 +68,7 @@ public async Task Handle(Command request, CancellationToken cancellatio throw new KeyNotFoundException("Room does not exist."); } - var nameExisted = await _context.Rooms.AnyAsync(x => x.Name - .ToLower().Equals(request.Name.ToLower()) - && x.Id != room.Id - , cancellationToken: cancellationToken); - - if (nameExisted) + if (await DuplicatedNameRoomExistsAsync(request.Name, request.RoomId, cancellationToken)) { throw new ConflictException("Name has already exists."); } @@ -74,26 +78,40 @@ public async Task Handle(Command request, CancellationToken cancellatio throw new ConflictException("New capacity cannot be less than current number of lockers."); } - var updatedRoom = new Room - { - Id = room.Id, - Name = request.Name, - Description = request.Description, - Staff = room.Staff, - Department = room.Department, - DepartmentId = room.DepartmentId, - Capacity = request.Capacity, - NumberOfLockers = room.NumberOfLockers, - IsAvailable = room.IsAvailable, - Lockers = room.Lockers - }; + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); - _context.Rooms.Entry(room).State = EntityState.Detached; - _context.Rooms.Entry(updatedRoom).State = EntityState.Modified; + // update work + room.Name = request.Name; + room.Description = request.Description; + room.Capacity = request.Capacity; + room.IsAvailable = request.IsAvailable; + room.LastModified = localDateTimeNow; + room.LastModifiedBy = request.CurrentUser.Id; + var log = new RoomLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = room.Id, + Time = localDateTimeNow, + Action = RoomLogMessage.Update, + }; + _context.Rooms.Entry(room).State = EntityState.Modified; + await _context.RoomLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - - return _mapper.Map(updatedRoom); + return _mapper.Map(room); + } + + private async Task DuplicatedNameRoomExistsAsync( + string roomName, + Guid roomId, + CancellationToken cancellationToken) + { + var room = await _context.Rooms.FirstOrDefaultAsync( + x => x.Name.Trim().ToLower().Equals(roomName.Trim().ToLower()) + && x.Id != roomId, + cancellationToken); + return room is not null; } } } \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetAllRoomLogsPaginated.cs b/src/Application/Rooms/Queries/GetAllRoomLogsPaginated.cs new file mode 100644 index 00000000..37b9f7ff --- /dev/null +++ b/src/Application/Rooms/Queries/GetAllRoomLogsPaginated.cs @@ -0,0 +1,61 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetAllRoomLogsPaginated +{ + public record Query : IRequest> + { + public Guid? RoomId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var logs = _context.RoomLogs + .Include(x => x.ObjectId) + .Include(x => x.User) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.RoomId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.RoomId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.ToLower().Contains(request.SearchTerm.ToLower())); + } + + return await logs + .LoggingListPaginateAsync( + request.Page, + request.Size, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs index ff89fe2b..b2961b8b 100644 --- a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs +++ b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs @@ -5,6 +5,8 @@ using Application.Common.Models.Dtos.Physical; using AutoMapper; using AutoMapper.QueryableExtensions; +using Domain.Entities; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -14,6 +16,8 @@ public class GetAllRoomsPaginated { public record Query : IRequest> { + public User CurrentUser { get; init; } = null!; + public Guid? DepartmentId { get; init; } public string? SearchTerm { get; init; } public int? Page { get; init; } public int? Size { get; init; } @@ -39,30 +43,31 @@ public async Task> Handle(Query request, CancellationToke .Include(x => x.Staff) .AsQueryable(); - if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + if ((request.CurrentUser.Role.IsStaff() || request.CurrentUser.Role.IsEmployee()) + && request.CurrentUser.Department?.Id != request.DepartmentId) { - rooms = rooms.Where(x => - x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + throw new UnauthorizedAccessException("User cannot access this resource."); } - - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) + + if (request.DepartmentId is not null) { - sortBy = nameof(RoomDto.Id); + rooms = rooms.Where(x => x.Department.Id == request.DepartmentId); } - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; - var count = await rooms.CountAsync(cancellationToken); - var list = await rooms - .OrderByCustom(sortBy, sortOrder) - .Paginate(pageNumber.Value, sizeNumber.Value) - .ToListAsync(cancellationToken); + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + rooms = rooms.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } - var result = _mapper.Map>(list); - - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + return await rooms + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); } } } \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetEmptyContainersPaginated.cs b/src/Application/Rooms/Queries/GetEmptyContainersPaginated.cs index 3534dacb..96157358 100644 --- a/src/Application/Rooms/Queries/GetEmptyContainersPaginated.cs +++ b/src/Application/Rooms/Queries/GetEmptyContainersPaginated.cs @@ -44,7 +44,7 @@ public async Task> Handle(Query request, Cancellat .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable() .ToList(); - + lockers.ForEach(x => x.Folders = x.Folders.Where(y => y.Slot > 0)); var result = new PaginatedList(lockers.ToList(), lockers.Count, pageNumber, sizeNumber); diff --git a/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs b/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs new file mode 100644 index 00000000..45e99e75 --- /dev/null +++ b/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs @@ -0,0 +1,43 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetRoomByDepartmentId +{ + public record Query : IRequest + { + public Guid DepartmentId { get; init; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var room = await _context.Rooms + .Include(x => x.Department) + .Include(x => x.Staff) + .FirstOrDefaultAsync(x => x.DepartmentId == request.DepartmentId, cancellationToken: cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + return _mapper.Map(room); + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetRoomById.cs b/src/Application/Rooms/Queries/GetRoomById.cs index 2d420d10..76532fbe 100644 --- a/src/Application/Rooms/Queries/GetRoomById.cs +++ b/src/Application/Rooms/Queries/GetRoomById.cs @@ -1,3 +1,4 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; using AutoMapper; @@ -11,6 +12,8 @@ public class GetRoomById { public record Query : IRequest { + public string CurrentUserRole { get; init; } = null!; + public Guid CurrentUserDepartmentId { get; init; } public Guid RoomId { get; init; } } @@ -30,14 +33,23 @@ public async Task Handle(Query request, CancellationToken cancellationT var room = await _context.Rooms .Include(x => x.Department) .Include(x => x.Staff) - .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); - + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken: cancellationToken); + if (room is null) { throw new KeyNotFoundException("Room does not exist."); } - + + if ((request.CurrentUserRole.IsStaff() || request.CurrentUserRole.IsEmployee()) + && !IsSameDepartment(request.CurrentUserDepartmentId, room.DepartmentId)) + { + throw new UnauthorizedAccessException("User cannot update this resource."); + } + return _mapper.Map(room); } + + private static bool IsSameDepartment(Guid departmentId1, Guid departmentId2) + => departmentId1 == departmentId2; } } \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetRoomByStaffId.cs b/src/Application/Rooms/Queries/GetRoomByStaffId.cs new file mode 100644 index 00000000..7a0c4f90 --- /dev/null +++ b/src/Application/Rooms/Queries/GetRoomByStaffId.cs @@ -0,0 +1,51 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetRoomByStaffId +{ + public record Query : IRequest + { + public Guid StaffId { get; init; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var staff = await _context.Staffs + .FirstOrDefaultAsync(x => x.Id == request.StaffId, cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exists."); + } + + var room = await _context.Rooms + .Include(x => x.Staff) + .ThenInclude(y => y!.User) + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Staff!.Id == request.StaffId, cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exists."); + } + + return _mapper.Map(room); + } + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/AddStaff.cs b/src/Application/Staffs/Commands/AddStaff.cs deleted file mode 100644 index 0d78536f..00000000 --- a/src/Application/Staffs/Commands/AddStaff.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Entities.Physical; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Staffs.Commands; - -public class AddStaff -{ - public record Command : IRequest - { - public Guid UserId { get; init; } - public Guid? RoomId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.UserId, cancellationToken); - if (user is null) - { - throw new KeyNotFoundException("User does not exist."); - } - - var room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); - - if (room is null) - { - throw new KeyNotFoundException("Room does not exist."); - } - - var existedStaff = await _context.Staffs - .Include(x => x.Room) - .Include(x => x.User) - .FirstOrDefaultAsync(x => x.Id == user.Id, cancellationToken); - if (existedStaff is not null) - { - if (existedStaff.Room is not null) - { - throw new ConflictException("This user is already a staff."); - } - - existedStaff.Room = room; - var result = _context.Staffs.Update(existedStaff); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - else - { - var staff = new Staff - { - Id = user.Id, - User = user, - Room = room - }; - - var result = await _context.Staffs.AddAsync(staff, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } - } -} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/AssignStaff.cs b/src/Application/Staffs/Commands/AssignStaff.cs new file mode 100644 index 00000000..3e569701 --- /dev/null +++ b/src/Application/Staffs/Commands/AssignStaff.cs @@ -0,0 +1,90 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Staffs.Commands; + +public class AssignStaff +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid StaffId { get; init; } + public Guid? RoomId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var staff = await _context.Staffs + .Include(x => x.User) + .FirstOrDefaultAsync(x => x.Id == request.StaffId, cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + var room = await _context.Rooms + .Include(x => x.Staff) + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + if (!room.IsAvailable) + { + throw new ConflictException("Room is not available."); + } + + if (room.Staff is not null) + { + throw new ConflictException("Room already has a staff."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + staff.Room = room; + room.Staff = staff; + room.LastModified = localDateTimeNow; + room.LastModifiedBy = request.CurrentUser.Id; + + var log = new UserLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = staff.User.Id, + Time = localDateTimeNow, + Action = UserLogMessages.Staff.AssignStaff(room.Id.ToString()), + }; + _context.Rooms.Update(room); + var result = _context.Staffs.Update(staff); + await _context.UserLogs.AddAsync(log, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + + } + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/RemoveStaff.cs b/src/Application/Staffs/Commands/RemoveStaff.cs deleted file mode 100644 index d550094b..00000000 --- a/src/Application/Staffs/Commands/RemoveStaff.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using FluentValidation; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Staffs.Commands; - -public class RemoveStaff -{ - public record Command : IRequest - { - public Guid StaffId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var staff = await _context.Staffs - .Include(x => x.User) - .Include(x => x.Room) - .FirstOrDefaultAsync(x => x.User.Id.Equals(request.StaffId), cancellationToken: cancellationToken); - - if (staff is null) - { - throw new KeyNotFoundException("Staff does not exist."); - } - - var result = _context.Staffs.Remove(staff); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs b/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs index b4e8be49..448c2093 100644 --- a/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs +++ b/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs @@ -1,9 +1,13 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Staffs.Commands; @@ -11,6 +15,7 @@ public class RemoveStaffFromRoom { public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid StaffId { get; init; } } @@ -18,11 +23,13 @@ public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) @@ -42,10 +49,22 @@ public async Task Handle(Command request, CancellationToken cancellati throw new ConflictException("Staff is not assigned to a room."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + staff.Room.Staff = null; _context.Rooms.Update(staff.Room!); staff.Room = null; + + var log = new UserLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = staff.User.Id, + Time = localDateTimeNow, + Action = UserLogMessages.Staff.RemoveFromRoom, + }; var result = _context.Staffs.Update(staff); + await _context.UserLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); diff --git a/src/Application/Staffs/EventHandlers/StaffCreatedEventHandler.cs b/src/Application/Staffs/EventHandlers/StaffCreatedEventHandler.cs new file mode 100644 index 00000000..2a8baf29 --- /dev/null +++ b/src/Application/Staffs/EventHandlers/StaffCreatedEventHandler.cs @@ -0,0 +1,28 @@ +using Application.Common.Interfaces; +using Domain.Entities.Physical; +using Domain.Events; +using MediatR; + +namespace Application.Staffs.EventHandlers; + +public class StaffCreatedEventHandler : INotificationHandler +{ + private readonly IApplicationDbContext _context; + + public StaffCreatedEventHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(StaffCreatedEvent notification, CancellationToken cancellationToken) + { + var staff = new Staff() + { + Id = notification.Staff.Id, + User = notification.Staff, + }; + + await _context.Staffs.AddAsync(staff, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Queries/GetAllStaffsPaginated.cs b/src/Application/Staffs/Queries/GetAllStaffsPaginated.cs index 61f62046..c0987de1 100644 --- a/src/Application/Staffs/Queries/GetAllStaffsPaginated.cs +++ b/src/Application/Staffs/Queries/GetAllStaffsPaginated.cs @@ -5,6 +5,7 @@ using Application.Common.Models.Dtos.Physical; using AutoMapper; using AutoMapper.QueryableExtensions; +using Domain.Entities.Physical; using MediatR; using Microsoft.EntityFrameworkCore; @@ -45,25 +46,14 @@ public async Task> Handle(Query request, CancellationTok staffs = staffs.Where(x => x.User.Username.ToLower().Contains(request.SearchTerm.ToLower())); } - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) - { - sortBy = nameof(StaffDto.Id); - } - - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; - - var count = await staffs.CountAsync(cancellationToken); - var list = await staffs - .OrderByCustom(sortBy, sortOrder) - .Paginate(pageNumber.Value,sizeNumber.Value) - .ToListAsync(cancellationToken); - - var result = _mapper.Map>(list); - - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + return await staffs + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); } } } \ No newline at end of file diff --git a/src/Application/Staffs/Queries/GetStaffByRoom.cs b/src/Application/Staffs/Queries/GetStaffByRoomId.cs similarity index 77% rename from src/Application/Staffs/Queries/GetStaffByRoom.cs rename to src/Application/Staffs/Queries/GetStaffByRoomId.cs index 60db1c83..119b96f1 100644 --- a/src/Application/Staffs/Queries/GetStaffByRoom.cs +++ b/src/Application/Staffs/Queries/GetStaffByRoomId.cs @@ -1,15 +1,18 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; using AutoMapper; +using Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; namespace Application.Staffs.Queries; -public class GetStaffByRoom +public class GetStaffByRoomId { public record Query : IRequest { + public User CurrentUser { get; init; } = null!; public Guid RoomId { get; init; } } @@ -37,6 +40,12 @@ public async Task Handle(Query request, CancellationToken cancellation throw new KeyNotFoundException("Room does not exist."); } + if (request.CurrentUser.Role.IsStaff() + && room.DepartmentId != request.CurrentUser.Department!.Id) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + if (room.Staff is null) { throw new KeyNotFoundException("Staff does not exist."); diff --git a/src/Application/Users/Commands/AddUser.cs b/src/Application/Users/Commands/AddUser.cs index 2ccef831..38c94551 100644 --- a/src/Application/Users/Commands/AddUser.cs +++ b/src/Application/Users/Commands/AddUser.cs @@ -1,10 +1,13 @@ using Application.Common.Exceptions; +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; using Application.Helpers; using Application.Identity; using Application.Users.Queries; using AutoMapper; using Domain.Entities; +using Domain.Entities.Logging; using Domain.Events; using FluentValidation; using MediatR; @@ -53,6 +56,7 @@ private static bool BeNotAdmin(string role) public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public string Username { get; init; } = null!; public string Email { get; init; } = null!; public string? FirstName { get; init; } @@ -67,16 +71,23 @@ public class AddUserCommandHandler : IRequestHandler private readonly IApplicationDbContext _context; private readonly IMapper _mapper; private readonly ISecurityService _securityService; + private readonly IDateTimeProvider _dateTimeProvider; - public AddUserCommandHandler(IApplicationDbContext context, IMapper mapper, ISecurityService securityService) + public AddUserCommandHandler(IApplicationDbContext context, IMapper mapper, ISecurityService securityService, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; _securityService = securityService; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { + if (request.Role.IsAdmin()) + { + throw new UnauthorizedAccessException(); + } + var user = await _context.Users.FirstOrDefaultAsync( x => x.Username.Equals(request.Username) || x.Email.Equals(request.Email), cancellationToken); @@ -96,6 +107,8 @@ public async Task Handle(Command request, CancellationToken cancellatio var password = StringUtil.RandomPassword(); var salt = StringUtil.RandomSalt(); + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var entity = new User { Username = request.Username, @@ -109,10 +122,27 @@ public async Task Handle(Command request, CancellationToken cancellatio Position = request.Position, IsActive = true, IsActivated = false, - Created = LocalDateTime.FromDateTime(DateTime.UtcNow) + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, }; + + entity.AddDomainEvent(new UserCreatedEvent(entity, password)); + if (request.Role.IsStaff()) + { + entity.AddDomainEvent(new StaffCreatedEvent(entity, request.CurrentUser)); + } var result = await _context.Users.AddAsync(entity, cancellationToken); + + var log = new UserLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = entity.Id, + Time = localDateTimeNow, + Action = UserLogMessages.Add(entity.Role), + }; + await _context.UserLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } diff --git a/src/Application/Users/Commands/DisableUser.cs b/src/Application/Users/Commands/DisableUser.cs deleted file mode 100644 index dafb4b31..00000000 --- a/src/Application/Users/Commands/DisableUser.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Users.Queries; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Users.Commands; - -public class DisableUser -{ - public record Command : IRequest - { - public Guid UserId { get; init; } - } - - public class CommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public CommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.UserId, cancellationToken); - if (user is null) - { - throw new KeyNotFoundException("User does not exist."); - } - - if (!user.IsActive) - { - throw new ConflictException("User has already been disabled."); - } - - user.IsActive = false; - - var result = _context.Users.Update(user); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } - } -} \ No newline at end of file diff --git a/src/Application/Users/Commands/EnableUser.cs b/src/Application/Users/Commands/EnableUser.cs deleted file mode 100644 index 7da8ab48..00000000 --- a/src/Application/Users/Commands/EnableUser.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Application.Users.Queries; -using MediatR; - -namespace Application.Users.Commands; - -public class EnableUser -{ - public record Command : IRequest - { - public Guid UserId { get; init; } - } -} \ No newline at end of file diff --git a/src/Application/Users/Commands/UpdateUser.cs b/src/Application/Users/Commands/UpdateUser.cs index 00b27531..8cb22bcd 100644 --- a/src/Application/Users/Commands/UpdateUser.cs +++ b/src/Application/Users/Commands/UpdateUser.cs @@ -1,9 +1,15 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Identity; using Application.Users.Queries; using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Application.Users.Commands; @@ -27,25 +33,36 @@ public Validator() } public record Command : IRequest { + public User CurrentUser { get; init; } = null!; public Guid UserId { get; init; } public string? FirstName { get; init; } public string? LastName { get; init; } public string? Position { get; init; } - } + public string Role { get; init; } = null!; + public bool IsActive { get; init; } } public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; - public CommandHandler(IApplicationDbContext context, IMapper mapper) + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider) { _context = context; _mapper = mapper; + _dateTimeProvider = dateTimeProvider; } public async Task Handle(Command request, CancellationToken cancellationToken) { + // save a roundtrip to db + if (request.CurrentUser.Role.IsAdmin() + && UpdateSelf(request.CurrentUser.Id, request.UserId)) + { + throw new UnauthorizedAccessException("User cannot update this resource."); + } + var user = await _context.Users .FirstOrDefaultAsync(x => x.Id.Equals(request.UserId), cancellationToken: cancellationToken); @@ -54,13 +71,31 @@ public async Task Handle(Command request, CancellationToken cancellatio throw new KeyNotFoundException("User does not exist."); } + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + user.FirstName = request.FirstName; user.LastName = request.LastName; user.Position = request.Position; - + user.Role = request.Role; + user.IsActive = request.IsActive; + user.LastModified = localDateTimeNow; + user.LastModifiedBy = request.CurrentUser.Id; + + var log = new UserLog() + { + User = request.CurrentUser, + UserId = request.CurrentUser.Id, + ObjectId = user.Id, + Time = localDateTimeNow, + Action = UserLogMessages.Update, + }; var result = _context.Users.Update(user); + await _context.UserLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); return _mapper.Map(result.Entity); } + + private static bool UpdateSelf(Guid currentUserId, Guid updatingUserId) + => updatingUserId == currentUserId; } } \ No newline at end of file diff --git a/src/Application/Users/Queries/GetAllUserLogsPaginated.cs b/src/Application/Users/Queries/GetAllUserLogsPaginated.cs new file mode 100644 index 00000000..ee1c3e7c --- /dev/null +++ b/src/Application/Users/Queries/GetAllUserLogsPaginated.cs @@ -0,0 +1,61 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Logging; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Logging; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Users.Queries; + +public class GetAllUserLogsPaginated +{ + public record Query : IRequest> + { + public Guid? UserId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var logs = _context.UserLogs + .Include(x => x.ObjectId) + .Include(x => x.User) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.UserId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.UserId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Action.Trim().ToLower().Contains(request.SearchTerm.Trim().ToLower())); + } + + return await logs + .LoggingListPaginateAsync( + request.Page, + request.Size, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Users/Queries/GetAllUsersPaginated.cs b/src/Application/Users/Queries/GetAllUsersPaginated.cs index f8613324..27d57746 100644 --- a/src/Application/Users/Queries/GetAllUsersPaginated.cs +++ b/src/Application/Users/Queries/GetAllUsersPaginated.cs @@ -1,13 +1,10 @@ using Application.Common.Extensions; using Application.Common.Interfaces; -using Application.Common.Mappings; using Application.Common.Models; -using Application.Common.Models.Dtos.Physical; using Application.Identity; using AutoMapper; -using AutoMapper.QueryableExtensions; +using Domain.Entities; using MediatR; -using Microsoft.EntityFrameworkCore; namespace Application.Users.Queries; @@ -15,7 +12,8 @@ public class GetAllUsersPaginated { public record Query : IRequest> { - public Guid? DepartmentId { get; init; } + public Guid[]? DepartmentIds { get; init; } + public string? Role { get; init; } public string? SearchTerm { get; init; } public int? Page { get; init; } public int? Size { get; init; } @@ -36,38 +34,36 @@ public QueryHandler(IApplicationDbContext context, IMapper mapper) public async Task> Handle(Query request, CancellationToken cancellationToken) { - var users = _context.Users.AsQueryable() + var users = _context.Users .Where(x => !x.Role.Equals(IdentityData.Roles.Admin)); - if (request.DepartmentId is not null) + // Filter by department + if (request.DepartmentIds is not null) { - users = users.Where(x => x.Department!.Id == request.DepartmentId); + users = users.Where(x => request.DepartmentIds.Contains(x.Department!.Id) ); + } + + // Filter by role + if (request.Role is not null) + { + users = users.Where(x => x.Role.ToLower().Equals(request.Role.Trim().ToLower())); } + // Search if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) { users = users.Where(x => x.FirstName!.ToLower().Contains(request.SearchTerm.Trim().ToLower())); } - var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) - { - sortBy = nameof(UserDto.Id); - } - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; - var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; - - var count = await users.CountAsync(cancellationToken); - var list = await users - .Paginate(pageNumber.Value, sizeNumber.Value) - .OrderByCustom(sortBy, sortOrder) - .ToListAsync(cancellationToken); - - var result = _mapper.Map>(list); - - return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + return await users + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); } } } \ No newline at end of file diff --git a/src/Application/Users/Queries/GetUserById.cs b/src/Application/Users/Queries/GetUserById.cs index 7b22fe51..d49ddb29 100644 --- a/src/Application/Users/Queries/GetUserById.cs +++ b/src/Application/Users/Queries/GetUserById.cs @@ -1,5 +1,8 @@ +using Application.Common.Extensions; using Application.Common.Interfaces; +using Application.Identity; using AutoMapper; +using Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; @@ -9,9 +12,11 @@ public class GetUserById { public record Query : IRequest { + public string UserRole { get; init; } = null!; + public Guid UserDepartmentId { get; init; } public Guid UserId { get; init; } } - + public class QueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; @@ -34,7 +39,16 @@ public async Task Handle(Query request, CancellationToken cancellationT throw new KeyNotFoundException("User does not exist."); } + if (ViolateConstraints(request.UserRole, request.UserDepartmentId, user)) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + return _mapper.Map(user); } + + private static bool ViolateConstraints(string userRole, Guid userDepartmentId, User foundUser) + => (userRole.IsStaff() || userRole.IsEmployee()) + && userDepartmentId != foundUser.Department?.Id; } } \ No newline at end of file diff --git a/src/Domain/Common/BaseLoggingEntity.cs b/src/Domain/Common/BaseLoggingEntity.cs new file mode 100644 index 00000000..d7379232 --- /dev/null +++ b/src/Domain/Common/BaseLoggingEntity.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using NodaTime; + +namespace Domain.Common; + +public class BaseLoggingEntity : BaseEntity +{ + public string Action { get; set; } = null!; + public Guid UserId { get; set; } + public Guid? ObjectId { get; set; } + public LocalDateTime Time { get; set; } + + public User User { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Domain/Entities/Department.cs b/src/Domain/Entities/Department.cs index b43bbfe7..6a353b38 100644 --- a/src/Domain/Entities/Department.cs +++ b/src/Domain/Entities/Department.cs @@ -6,5 +6,6 @@ namespace Domain.Entities; public class Department : BaseEntity { public string Name { get; set; } = null!; - public Room? Room { get; set; } + + public ICollection Rooms { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Domain/Entities/Logging/DocumentLog.cs b/src/Domain/Entities/Logging/DocumentLog.cs new file mode 100644 index 00000000..e52fae0c --- /dev/null +++ b/src/Domain/Entities/Logging/DocumentLog.cs @@ -0,0 +1,9 @@ +using Domain.Common; +using Domain.Entities.Physical; + +namespace Domain.Entities.Logging; + +public class DocumentLog : BaseLoggingEntity +{ + public Folder? BaseFolder { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/Logging/FolderLog.cs b/src/Domain/Entities/Logging/FolderLog.cs new file mode 100644 index 00000000..e41cbdf2 --- /dev/null +++ b/src/Domain/Entities/Logging/FolderLog.cs @@ -0,0 +1,9 @@ +using Domain.Common; +using Domain.Entities.Physical; + +namespace Domain.Entities.Logging; + +public class FolderLog : BaseLoggingEntity +{ + public Locker? BaseLocker { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/Logging/LockerLog.cs b/src/Domain/Entities/Logging/LockerLog.cs new file mode 100644 index 00000000..96c8ea44 --- /dev/null +++ b/src/Domain/Entities/Logging/LockerLog.cs @@ -0,0 +1,9 @@ +using Domain.Common; +using Domain.Entities.Physical; + +namespace Domain.Entities.Logging; + +public class LockerLog : BaseLoggingEntity +{ + public Room? BaseRoom { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/Logging/RequestLog.cs b/src/Domain/Entities/Logging/RequestLog.cs new file mode 100644 index 00000000..5e801092 --- /dev/null +++ b/src/Domain/Entities/Logging/RequestLog.cs @@ -0,0 +1,10 @@ +using Domain.Common; +using Domain.Entities.Physical; +using Domain.Enums; + +namespace Domain.Entities.Logging; + +public class RequestLog : BaseLoggingEntity +{ + public RequestType Type { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/Logging/RoomLog.cs b/src/Domain/Entities/Logging/RoomLog.cs new file mode 100644 index 00000000..b662a46f --- /dev/null +++ b/src/Domain/Entities/Logging/RoomLog.cs @@ -0,0 +1,8 @@ +using Domain.Common; +using Domain.Entities.Physical; + +namespace Domain.Entities.Logging; + +public class RoomLog : BaseLoggingEntity +{ +} \ No newline at end of file diff --git a/src/Domain/Entities/Logging/UserLog.cs b/src/Domain/Entities/Logging/UserLog.cs new file mode 100644 index 00000000..32735697 --- /dev/null +++ b/src/Domain/Entities/Logging/UserLog.cs @@ -0,0 +1,7 @@ +using Domain.Common; + +namespace Domain.Entities.Logging; + +public class UserLog : BaseLoggingEntity +{ +} \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Borrow.cs b/src/Domain/Entities/Physical/Borrow.cs index 8b3076ac..bd05231a 100644 --- a/src/Domain/Entities/Physical/Borrow.cs +++ b/src/Domain/Entities/Physical/Borrow.cs @@ -4,13 +4,14 @@ namespace Domain.Entities.Physical; -public class Borrow : BaseEntity +public class Borrow : BaseAuditableEntity { public User Borrower { get; set; } = null!; public Document Document { get; set; } = null!; public LocalDateTime BorrowTime { get; set; } public LocalDateTime DueTime { get; set; } public LocalDateTime ActualReturnTime { get; set; } - public string Reason { get; set; } = null!; + public string BorrowReason { get; set; } = null!; + public string StaffReason { get; set; } = null!; public BorrowRequestStatus Status { get; set; } } \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Document.cs b/src/Domain/Entities/Physical/Document.cs index a473a2b5..0de04af1 100644 --- a/src/Domain/Entities/Physical/Document.cs +++ b/src/Domain/Entities/Physical/Document.cs @@ -4,16 +4,18 @@ namespace Domain.Entities.Physical; -public class Document : BaseEntity +public class Document : BaseAuditableEntity { public string Title { get; set; } = null!; public string? Description { get; set; } public string DocumentType { get; set; } = null!; + public Guid? ImporterId { get; set; } public Department? Department { get; set; } - public User? Importer { get; set; } public Folder? Folder { get; set; } public DocumentStatus Status { get; set; } public Guid? EntryId { get; set; } + public bool IsPrivate { get; set; } + public User? Importer { get; set; } public virtual Entry? Entry { get; set; } } \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Folder.cs b/src/Domain/Entities/Physical/Folder.cs index 596e648c..bb7aacee 100644 --- a/src/Domain/Entities/Physical/Folder.cs +++ b/src/Domain/Entities/Physical/Folder.cs @@ -2,7 +2,7 @@ namespace Domain.Entities.Physical; -public class Folder : BaseEntity +public class Folder : BaseAuditableEntity { public string Name { get; set; } = null!; public string? Description { get; set; } diff --git a/src/Domain/Entities/Physical/ImportRequest.cs b/src/Domain/Entities/Physical/ImportRequest.cs new file mode 100644 index 00000000..b5060d88 --- /dev/null +++ b/src/Domain/Entities/Physical/ImportRequest.cs @@ -0,0 +1,16 @@ +using Domain.Common; +using Domain.Statuses; + +namespace Domain.Entities.Physical; + +public class ImportRequest : BaseAuditableEntity +{ + public Guid RoomId { get; set; } + public Guid DocumentId { get; set; } + public string ImportReason { get; set; } = null!; + public string StaffReason { get; set; } = null!; + public ImportRequestStatus Status { get; set; } + + public Room Room { get; set; } = null!; + public Document Document { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Locker.cs b/src/Domain/Entities/Physical/Locker.cs index 5f0af84c..3bd9f836 100644 --- a/src/Domain/Entities/Physical/Locker.cs +++ b/src/Domain/Entities/Physical/Locker.cs @@ -2,7 +2,7 @@ namespace Domain.Entities.Physical; -public class Locker : BaseEntity +public class Locker : BaseAuditableEntity { public string Name { get; set; } = null!; public string? Description { get; set; } diff --git a/src/Domain/Entities/Physical/Permission.cs b/src/Domain/Entities/Physical/Permission.cs new file mode 100644 index 00000000..29b01181 --- /dev/null +++ b/src/Domain/Entities/Physical/Permission.cs @@ -0,0 +1,14 @@ +using NodaTime; + +namespace Domain.Entities.Physical; + +public class Permission +{ + public Guid EmployeeId { get; set; } + public Guid DocumentId { get; set; } + public string AllowedOperations { get; set; } = null!; + public LocalDateTime ExpiryDateTime { get; set; } + + public User Employee { get; set; } = null!; + public Document Document { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Room.cs b/src/Domain/Entities/Physical/Room.cs index 3d2318ac..e6b988db 100644 --- a/src/Domain/Entities/Physical/Room.cs +++ b/src/Domain/Entities/Physical/Room.cs @@ -2,7 +2,7 @@ namespace Domain.Entities.Physical; -public class Room : BaseEntity +public class Room : BaseAuditableEntity { public string Name { get; set; } = null!; public string? Description { get; set; } diff --git a/src/Domain/Enums/RequestType.cs b/src/Domain/Enums/RequestType.cs new file mode 100644 index 00000000..f6dbac41 --- /dev/null +++ b/src/Domain/Enums/RequestType.cs @@ -0,0 +1,7 @@ +namespace Domain.Enums; + +public enum RequestType +{ + Import, + Borrow, +} \ No newline at end of file diff --git a/src/Domain/Events/StaffCreatedEvent.cs b/src/Domain/Events/StaffCreatedEvent.cs new file mode 100644 index 00000000..2b6adfb1 --- /dev/null +++ b/src/Domain/Events/StaffCreatedEvent.cs @@ -0,0 +1,16 @@ +using Domain.Common; +using Domain.Entities; + +namespace Domain.Events; + +public class StaffCreatedEvent : BaseEvent +{ + public StaffCreatedEvent(User staff, User currentUser) + { + Staff = staff; + CurrentUser = currentUser; + } + + public User Staff { get; } + public User CurrentUser { get; } +} \ No newline at end of file diff --git a/src/Domain/Statuses/BorrowRequestStatus.cs b/src/Domain/Statuses/BorrowRequestStatus.cs index 15c19a0b..8ef13936 100644 --- a/src/Domain/Statuses/BorrowRequestStatus.cs +++ b/src/Domain/Statuses/BorrowRequestStatus.cs @@ -2,13 +2,13 @@ namespace Domain.Statuses; public enum BorrowRequestStatus { - Approved, Pending, + Approved, Rejected, - Overdue, - Cancelled, CheckedOut, Returned, + Overdue, + Cancelled, Lost, NotProcessable, } \ No newline at end of file diff --git a/src/Domain/Statuses/ImportRequestStatus.cs b/src/Domain/Statuses/ImportRequestStatus.cs new file mode 100644 index 00000000..65a65ffc --- /dev/null +++ b/src/Domain/Statuses/ImportRequestStatus.cs @@ -0,0 +1,9 @@ +namespace Domain.Statuses; + +public enum ImportRequestStatus +{ + Pending, + Approved, + Rejected, + CheckedIn, +} \ No newline at end of file diff --git a/src/Infrastructure/ConfigureServices.cs b/src/Infrastructure/ConfigureServices.cs index 3d4ef3bb..710b935b 100644 --- a/src/Infrastructure/ConfigureServices.cs +++ b/src/Infrastructure/ConfigureServices.cs @@ -1,10 +1,13 @@ +using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography; using Application.Common.Interfaces; +using Application.Common.Models; using Infrastructure.Identity; using Infrastructure.Identity.Authentication; using Infrastructure.Persistence; using Infrastructure.Services; using Infrastructure.Shared; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -20,6 +23,8 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti services.AddScoped(sp => sp.GetService()!); services.AddScoped(sp => sp.GetService()!); services.AddScoped(); + services.AddScoped(); + services.AddTransient(); services.AddMailService(configuration); services.AddJweAuthentication(configuration); @@ -82,7 +87,7 @@ private static IServiceCollection AddJweAuthentication(this IServiceCollection s services.AddSingleton(encryptionKey); services.AddSingleton(signingKey); services.AddSingleton(tokenValidationParameters); - + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(JweAuthenticationOptions.DefaultScheme) .AddScheme(JweAuthenticationOptions.DefaultScheme, options => diff --git a/src/Infrastructure/Identity/Authentication/JweAuthenticationHandler.cs b/src/Infrastructure/Identity/Authentication/JweAuthenticationHandler.cs index 795d1585..8c706291 100644 --- a/src/Infrastructure/Identity/Authentication/JweAuthenticationHandler.cs +++ b/src/Infrastructure/Identity/Authentication/JweAuthenticationHandler.cs @@ -40,6 +40,10 @@ protected override async Task HandleAuthenticateAsync() var claimsPrincipal = handler.ValidateToken(token, Options.TokenValidationParameters, out var validatedToken); + if (claimsPrincipal.Claims.Single(x => x.Type.Equals("isActive")).Value.Equals(false.ToString())) + { + return AuthenticateResult.Fail("User is not active."); + } Context.User = claimsPrincipal; return validatedToken is null diff --git a/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs b/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs index 863a36d7..469e1e98 100644 --- a/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs +++ b/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs @@ -1,3 +1,4 @@ +using System.IdentityModel.Tokens.Jwt; using Application.Identity; using Infrastructure.Persistence; using Microsoft.AspNetCore.Mvc; @@ -20,8 +21,7 @@ public void OnAuthorization(AuthorizationFilterContext context) { var dbContext = context.HttpContext.RequestServices.GetRequiredService(); - const string emailClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; - var email = context.HttpContext.User.Claims.SingleOrDefault(y => y.Type.Equals(emailClaim))!.Value; + var email = context.HttpContext.User.Claims.SingleOrDefault(y => y.Type.Equals(JwtRegisteredClaimNames.Email))!.Value; var user = dbContext.Users.FirstOrDefault(x => x.Email!.Equals(email)); diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs index ef335a22..ddf84780 100644 --- a/src/Infrastructure/Identity/IdentityService.cs +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -60,9 +60,7 @@ public async Task Validate(string token, string refreshToken) return false; } - const string emailClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; - - var email = validatedToken.Claims.Single(y => y.Type.Equals(emailClaim)).Value; + var email = validatedToken.Claims.Single(y => y.Type.Equals(JwtRegisteredClaimNames.Email)).Value; var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Username.Equals(email) @@ -118,12 +116,12 @@ public async Task RefreshTokenAsync(string token, string r { throw new AuthenticationException("Invalid token."); } - - const string emailClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; - var email = validatedToken.Claims.Single(y => y.Type.Equals(emailClaim)).Value; + var email = validatedToken.Claims.Single(y => y.Type.Equals(JwtRegisteredClaimNames.Email)).Value; - var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => + var user = await _applicationDbContext.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Username.Equals(email) || x.Email!.Equals(email)); @@ -197,7 +195,7 @@ public async Task RefreshTokenAsync(string token, string r { var user = _applicationDbContext.Users .Include(x => x.Department) - .FirstOrDefault(x => x.Email!.Equals(email)); + .FirstOrDefault(x => x.Email.ToLower().Equals(email.Trim().ToLower())); if (user is null || !user.PasswordHash.Equals(password.HashPasswordWith(user.PasswordSalt, _securitySettings.Pepper))) { @@ -260,7 +258,7 @@ public async Task ResetPassword(string token, string newPassword) } var salt = StringUtil.RandomSalt(); user.PasswordSalt = salt; - user.PasswordHash = newPassword.HashPasswordWith(salt, newPassword); + user.PasswordHash = newPassword.HashPasswordWith(salt, _securitySettings.Pepper); resetPasswordToken.IsInvalidated = true; await _applicationDbContext.SaveChangesAsync(CancellationToken.None); await _authDbContext.SaveChangesAsync(CancellationToken.None); @@ -293,10 +291,13 @@ private SecurityToken CreateJweToken(User user) var utcNow = DateTime.UtcNow; var authClaims = new List { + new(JwtRegisteredClaimNames.NameId, user.Id.ToString()), new(JwtRegisteredClaimNames.Sub, user.Username), new(JwtRegisteredClaimNames.Email, user.Email!), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new(JwtRegisteredClaimNames.Iat, utcNow.ToString(CultureInfo.InvariantCulture)), + new("departmentId", user.Department is not null ? user.Department.Id.ToString() : Guid.Empty.ToString()), + new("isActive", user.IsActive.ToString()), }; var publicEncryptionKey = new RsaSecurityKey(_encryptionKey.ExportParameters(false)) {KeyId = _jweSettings.EncryptionKeyId}; var privateSigningKey = new ECDsaSecurityKey(_signingKey) {KeyId = _jweSettings.SigningKeyId}; diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 8b76879b..82d1f679 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Infrastructure/Persistence/ApplicationDbContext.cs index 91c14162..c842828d 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContext.cs @@ -2,6 +2,7 @@ using Application.Common.Interfaces; using Domain.Entities; using Domain.Entities.Digital; +using Domain.Entities.Logging; using Domain.Entities.Physical; using Infrastructure.Common; using MediatR; @@ -26,7 +27,9 @@ public ApplicationDbContext( public DbSet Lockers => Set(); public DbSet Folders => Set(); public DbSet Documents => Set(); + public DbSet ImportRequests => Set(); public DbSet Borrows => Set(); + public DbSet Permissions => Set(); public DbSet UserGroups => Set(); public DbSet Files => Set(); @@ -34,6 +37,14 @@ public ApplicationDbContext( public DbSet RefreshTokens => Set(); public DbSet ResetPasswordTokens => Set(); + + public DbSet RoomLogs => Set(); + public DbSet LockerLogs => Set(); + public DbSet FolderLogs => Set(); + public DbSet DocumentLogs => Set(); + public DbSet RequestLogs => Set(); + public DbSet UserLogs => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index f788d804..394444b2 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -86,11 +86,6 @@ private static async Task TrySeedAsync(ApplicationDbContext context, string pepp admin.Department = department; await context.Users.AddAsync(admin); } - if (context.Users.All(u => u.Username != staff.Username)) - { - staff.Department = department; - await context.Users.AddAsync(staff); - } } else { @@ -100,11 +95,6 @@ private static async Task TrySeedAsync(ApplicationDbContext context, string pepp admin.Department = departmentEntity; await context.Users.AddAsync(admin); } - if (context.Users.All(u => u.Username != staff.Username)) - { - staff.Department = departmentEntity; - await context.Users.AddAsync(staff); - } } if (context.Departments.All(u => u.Name != itDepartment.Name)) @@ -115,6 +105,11 @@ private static async Task TrySeedAsync(ApplicationDbContext context, string pepp employee.Department = itDepartment; await context.Users.AddAsync(employee); } + if (context.Users.All(u => u.Username != staff.Username)) + { + staff.Department = department; + await context.Users.AddAsync(staff); + } } else { @@ -124,6 +119,11 @@ private static async Task TrySeedAsync(ApplicationDbContext context, string pepp employee.Department = departmentEntity; await context.Users.AddAsync(employee); } + if (context.Users.All(u => u.Username != staff.Username)) + { + staff.Department = departmentEntity; + await context.Users.AddAsync(staff); + } } await context.SaveChangesAsync(); diff --git a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs index 78697f26..7b855437 100644 --- a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs @@ -28,7 +28,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.DueTime) .IsRequired(); - builder.Property(x => x.Reason) + builder.Property(x => x.BorrowReason) .IsRequired(); builder.Property(x => x.Status) diff --git a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs index a9d89058..3bb00c66 100644 --- a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs @@ -36,7 +36,7 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(x => x.Importer) .WithMany() - .HasForeignKey("ImporterId") + .HasForeignKey(x => x.ImporterId) .IsRequired(false); builder.Property(x => x.Status) @@ -46,5 +46,8 @@ public void Configure(EntityTypeBuilder builder) .WithOne() .HasForeignKey(x => x.EntryId) .IsRequired(false); + + builder.Property(x => x.IsPrivate) + .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/ImportRequestConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ImportRequestConfiguration.cs new file mode 100644 index 00000000..c9e4e1b6 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/ImportRequestConfiguration.cs @@ -0,0 +1,43 @@ +using Domain.Entities.Physical; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class ImportRequestConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .ValueGeneratedOnAdd(); + + builder.HasOne(x => x.Document) + .WithOne() + .HasForeignKey(x => x.DocumentId) + .IsRequired(); + + builder.HasOne(x => x.Room) + .WithMany() + .HasForeignKey(x => x.RoomId) + .IsRequired(); + + builder.Property(x => x.ImportReason) + .IsRequired(); + + builder.Property(x => x.Status) + .IsRequired(); + + builder.Property(x => x.Created) + .IsRequired(); + + builder.Property(x => x.CreatedBy) + .IsRequired(false); + + builder.Property(x => x.LastModified) + .IsRequired(false); + + builder.Property(x => x.LastModifiedBy) + .IsRequired(false); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/PermissionConfiguration.cs b/src/Infrastructure/Persistence/Configurations/PermissionConfiguration.cs new file mode 100644 index 00000000..0f5db055 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/PermissionConfiguration.cs @@ -0,0 +1,16 @@ +using Domain.Entities.Physical; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class PermissionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => new { x.DocumentId, x.EmployeeId }); + + builder.Property(x => x.AllowedOperations) + .IsRequired(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs b/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs index 2ad65961..98b4fea3 100644 --- a/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs @@ -1,4 +1,3 @@ -using Domain.Entities; using Domain.Entities.Physical; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -27,10 +26,16 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.NumberOfLockers) .IsRequired(); + builder.Property(x => x.Capacity) .IsRequired(); builder.Property(x => x.IsAvailable) .IsRequired(); + + builder.HasOne(x => x.Department) + .WithMany(x => x.Rooms) + .HasForeignKey(x => x.DepartmentId) + .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/UserLogConfiguration.cs b/src/Infrastructure/Persistence/Configurations/UserLogConfiguration.cs new file mode 100644 index 00000000..09f6db4c --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/UserLogConfiguration.cs @@ -0,0 +1,20 @@ +using Domain.Entities.Logging; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class UserLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .ValueGeneratedOnAdd(); + + builder.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .IsRequired(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.Designer.cs new file mode 100644 index 00000000..69cd1b45 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.Designer.cs @@ -0,0 +1,879 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230612222216_Logging")] + partial class Logging + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.cs b/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.cs new file mode 100644 index 00000000..322b6a3c --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612222216_Logging.cs @@ -0,0 +1,381 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class Logging : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Created", + table: "Rooms", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Rooms", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Rooms", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Rooms", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "Created", + table: "Lockers", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Lockers", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Lockers", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Lockers", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "Created", + table: "Folders", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Folders", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Folders", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Folders", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "Created", + table: "Documents", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Documents", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Documents", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Documents", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "Created", + table: "Borrows", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Borrows", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Borrows", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Borrows", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "DocumentLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DocumentLogs", x => x.Id); + table.ForeignKey( + name: "FK_DocumentLogs_Documents_ObjectId", + column: x => x.ObjectId, + principalTable: "Documents", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_DocumentLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "FolderLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FolderLogs", x => x.Id); + table.ForeignKey( + name: "FK_FolderLogs_Folders_ObjectId", + column: x => x.ObjectId, + principalTable: "Folders", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_FolderLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LockerLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LockerLogs", x => x.Id); + table.ForeignKey( + name: "FK_LockerLogs_Lockers_ObjectId", + column: x => x.ObjectId, + principalTable: "Lockers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_LockerLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RoomLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoomLogs", x => x.Id); + table.ForeignKey( + name: "FK_RoomLogs_Rooms_ObjectId", + column: x => x.ObjectId, + principalTable: "Rooms", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_RoomLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_ObjectId", + table: "DocumentLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_UserId", + table: "DocumentLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_ObjectId", + table: "FolderLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_UserId", + table: "FolderLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_ObjectId", + table: "LockerLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_UserId", + table: "LockerLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomLogs_ObjectId", + table: "RoomLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomLogs_UserId", + table: "RoomLogs", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DocumentLogs"); + + migrationBuilder.DropTable( + name: "FolderLogs"); + + migrationBuilder.DropTable( + name: "LockerLogs"); + + migrationBuilder.DropTable( + name: "RoomLogs"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Lockers"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Lockers"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Lockers"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Lockers"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Folders"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Folders"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Folders"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Folders"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Borrows"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Borrows"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Borrows"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Borrows"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.Designer.cs new file mode 100644 index 00000000..303b1ca8 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.Designer.cs @@ -0,0 +1,917 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230612223900_Permission")] + partial class Permission + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.cs b/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.cs new file mode 100644 index 00000000..d910fecb --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612223900_Permission.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class Permission : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Permissions", + columns: table => new + { + EmployeeId = table.Column(type: "uuid", nullable: false), + DocumentId = table.Column(type: "uuid", nullable: false), + AllowedOperations = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => new { x.DocumentId, x.EmployeeId }); + table.ForeignKey( + name: "FK_Permissions_Documents_DocumentId", + column: x => x.DocumentId, + principalTable: "Documents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Permissions_Users_EmployeeId", + column: x => x.EmployeeId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_EmployeeId", + table: "Permissions", + column: "EmployeeId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Permissions"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.Designer.cs new file mode 100644 index 00000000..b34b7bfe --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.Designer.cs @@ -0,0 +1,965 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230612230312_RequestLog")] + partial class RequestLog + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.cs b/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.cs new file mode 100644 index 00000000..e06dcb91 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612230312_RequestLog.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class RequestLog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RequestLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Type = table.Column(type: "integer", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestLogs", x => x.Id); + table.ForeignKey( + name: "FK_RequestLogs_Documents_ObjectId", + column: x => x.ObjectId, + principalTable: "Documents", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_RequestLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RequestLogs_ObjectId", + table: "RequestLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_RequestLogs_UserId", + table: "RequestLogs", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RequestLogs"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.Designer.cs new file mode 100644 index 00000000..0974cf5a --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.Designer.cs @@ -0,0 +1,968 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230612231014_DocumentVisibility")] + partial class DocumentVisibility + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.cs b/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.cs new file mode 100644 index 00000000..673b2ad5 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612231014_DocumentVisibility.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class DocumentVisibility : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsPrivate", + table: "Documents", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsPrivate", + table: "Documents"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.Designer.cs new file mode 100644 index 00000000..ea6e1761 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.Designer.cs @@ -0,0 +1,1015 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230612231759_UserLog")] + partial class UserLog + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.cs b/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.cs new file mode 100644 index 00000000..c26f9e4b --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230612231759_UserLog.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class UserLog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: false), + Time = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserLogs", x => x.Id); + table.ForeignKey( + name: "FK_UserLogs_Users_ObjectId", + column: x => x.ObjectId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserLogs_ObjectId", + table: "UserLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_UserLogs_UserId", + table: "UserLogs", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserLogs"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs new file mode 100644 index 00000000..710cd220 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs @@ -0,0 +1,1019 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230613140433_RequestLogReason")] + partial class RequestLogReason + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs new file mode 100644 index 00000000..5ca28aa1 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class RequestLogReason : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Reason", + table: "RequestLogs", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Reason", + table: "RequestLogs"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.Designer.cs new file mode 100644 index 00000000..9b76af20 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.Designer.cs @@ -0,0 +1,1022 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230614084428_PermissionExpiry")] + partial class PermissionExpiry + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.cs b/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.cs new file mode 100644 index 00000000..61e9daaa --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230614084428_PermissionExpiry.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class PermissionExpiry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ExpiryDateTime", + table: "Permissions", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ExpiryDateTime", + table: "Permissions"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.Designer.cs new file mode 100644 index 00000000..aa6b5101 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.Designer.cs @@ -0,0 +1,1021 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230616101721_DepartmentHasManyRooms")] + partial class DepartmentHasManyRooms + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.cs b/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.cs new file mode 100644 index 00000000..4d7f402c --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230616101721_DepartmentHasManyRooms.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class DepartmentHasManyRooms : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms", + column: "DepartmentId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms"); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms", + column: "DepartmentId", + unique: true); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.Designer.cs new file mode 100644 index 00000000..dc0a573d --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.Designer.cs @@ -0,0 +1,1076 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230618044742_AddImportRequest")] + partial class AddImportRequest + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.cs b/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.cs new file mode 100644 index 00000000..67c80471 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618044742_AddImportRequest.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddImportRequest : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ImportRequests", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + RoomId = table.Column(type: "uuid", nullable: false), + DocumentId = table.Column(type: "uuid", nullable: false), + Status = table.Column(type: "integer", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: true), + LastModified = table.Column(type: "timestamp without time zone", nullable: true), + LastModifiedBy = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ImportRequests", x => x.Id); + table.ForeignKey( + name: "FK_ImportRequests_Documents_DocumentId", + column: x => x.DocumentId, + principalTable: "Documents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ImportRequests_Rooms_RoomId", + column: x => x.RoomId, + principalTable: "Rooms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ImportRequests_DocumentId", + table: "ImportRequests", + column: "DocumentId"); + + migrationBuilder.CreateIndex( + name: "IX_ImportRequests_RoomId", + table: "ImportRequests", + column: "RoomId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ImportRequests"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.Designer.cs new file mode 100644 index 00000000..a64e8b12 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.Designer.cs @@ -0,0 +1,1080 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230618051340_AddImportRequestReason")] + partial class AddImportRequestReason + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.cs b/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.cs new file mode 100644 index 00000000..930e4718 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618051340_AddImportRequestReason.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddImportRequestReason : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Reason", + table: "ImportRequests", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Reason", + table: "ImportRequests"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.Designer.cs new file mode 100644 index 00000000..531983a1 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.Designer.cs @@ -0,0 +1,1110 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230618133410_LoggingNowHasBaseObject")] + partial class LoggingNowHasBaseObject + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseFolderId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseFolderId"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseLockerId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseLockerId"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseRoomId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseRoomId"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "BaseFolder") + .WithMany() + .HasForeignKey("BaseFolderId"); + + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseFolder"); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "BaseLocker") + .WithMany() + .HasForeignKey("BaseLockerId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseLocker"); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "BaseRoom") + .WithMany() + .HasForeignKey("BaseRoomId"); + + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseRoom"); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.ImportRequest", "DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.cs b/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.cs new file mode 100644 index 00000000..64028226 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230618133410_LoggingNowHasBaseObject.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class LoggingNowHasBaseObject : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_ImportRequests_DocumentId", + table: "ImportRequests"); + + migrationBuilder.DropColumn( + name: "Reason", + table: "RequestLogs"); + + migrationBuilder.AddColumn( + name: "BaseRoomId", + table: "LockerLogs", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseLockerId", + table: "FolderLogs", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseFolderId", + table: "DocumentLogs", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_BaseRoomId", + table: "LockerLogs", + column: "BaseRoomId"); + + migrationBuilder.CreateIndex( + name: "IX_ImportRequests_DocumentId", + table: "ImportRequests", + column: "DocumentId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_BaseLockerId", + table: "FolderLogs", + column: "BaseLockerId"); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_BaseFolderId", + table: "DocumentLogs", + column: "BaseFolderId"); + + migrationBuilder.AddForeignKey( + name: "FK_DocumentLogs_Folders_BaseFolderId", + table: "DocumentLogs", + column: "BaseFolderId", + principalTable: "Folders", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_FolderLogs_Lockers_BaseLockerId", + table: "FolderLogs", + column: "BaseLockerId", + principalTable: "Lockers", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_LockerLogs_Rooms_BaseRoomId", + table: "LockerLogs", + column: "BaseRoomId", + principalTable: "Rooms", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DocumentLogs_Folders_BaseFolderId", + table: "DocumentLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_FolderLogs_Lockers_BaseLockerId", + table: "FolderLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_LockerLogs_Rooms_BaseRoomId", + table: "LockerLogs"); + + migrationBuilder.DropIndex( + name: "IX_LockerLogs_BaseRoomId", + table: "LockerLogs"); + + migrationBuilder.DropIndex( + name: "IX_ImportRequests_DocumentId", + table: "ImportRequests"); + + migrationBuilder.DropIndex( + name: "IX_FolderLogs_BaseLockerId", + table: "FolderLogs"); + + migrationBuilder.DropIndex( + name: "IX_DocumentLogs_BaseFolderId", + table: "DocumentLogs"); + + migrationBuilder.DropColumn( + name: "BaseRoomId", + table: "LockerLogs"); + + migrationBuilder.DropColumn( + name: "BaseLockerId", + table: "FolderLogs"); + + migrationBuilder.DropColumn( + name: "BaseFolderId", + table: "DocumentLogs"); + + migrationBuilder.AddColumn( + name: "Reason", + table: "RequestLogs", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateIndex( + name: "IX_ImportRequests_DocumentId", + table: "ImportRequests", + column: "DocumentId"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.Designer.cs new file mode 100644 index 00000000..77f26586 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.Designer.cs @@ -0,0 +1,1060 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230620131154_LoggingNowHasObjectId")] + partial class LoggingNowHasObjectId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseFolderId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseFolderId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseLockerId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseLockerId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseRoomId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseRoomId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "BaseFolder") + .WithMany() + .HasForeignKey("BaseFolderId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseFolder"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "BaseLocker") + .WithMany() + .HasForeignKey("BaseLockerId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseLocker"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "BaseRoom") + .WithMany() + .HasForeignKey("BaseRoomId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseRoom"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.ImportRequest", "DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.cs b/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.cs new file mode 100644 index 00000000..28d5c38d --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230620131154_LoggingNowHasObjectId.cs @@ -0,0 +1,158 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class LoggingNowHasObjectId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_DocumentLogs_Documents_ObjectId", + table: "DocumentLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_FolderLogs_Folders_ObjectId", + table: "FolderLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_LockerLogs_Lockers_ObjectId", + table: "LockerLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_RequestLogs_Documents_ObjectId", + table: "RequestLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_RoomLogs_Rooms_ObjectId", + table: "RoomLogs"); + + migrationBuilder.DropForeignKey( + name: "FK_UserLogs_Users_ObjectId", + table: "UserLogs"); + + migrationBuilder.DropIndex( + name: "IX_UserLogs_ObjectId", + table: "UserLogs"); + + migrationBuilder.DropIndex( + name: "IX_RoomLogs_ObjectId", + table: "RoomLogs"); + + migrationBuilder.DropIndex( + name: "IX_RequestLogs_ObjectId", + table: "RequestLogs"); + + migrationBuilder.DropIndex( + name: "IX_LockerLogs_ObjectId", + table: "LockerLogs"); + + migrationBuilder.DropIndex( + name: "IX_FolderLogs_ObjectId", + table: "FolderLogs"); + + migrationBuilder.DropIndex( + name: "IX_DocumentLogs_ObjectId", + table: "DocumentLogs"); + + migrationBuilder.AlterColumn( + name: "ObjectId", + table: "UserLogs", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ObjectId", + table: "UserLogs", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.CreateIndex( + name: "IX_UserLogs_ObjectId", + table: "UserLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomLogs_ObjectId", + table: "RoomLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_RequestLogs_ObjectId", + table: "RequestLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_ObjectId", + table: "LockerLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_ObjectId", + table: "FolderLogs", + column: "ObjectId"); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_ObjectId", + table: "DocumentLogs", + column: "ObjectId"); + + migrationBuilder.AddForeignKey( + name: "FK_DocumentLogs_Documents_ObjectId", + table: "DocumentLogs", + column: "ObjectId", + principalTable: "Documents", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_FolderLogs_Folders_ObjectId", + table: "FolderLogs", + column: "ObjectId", + principalTable: "Folders", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_LockerLogs_Lockers_ObjectId", + table: "LockerLogs", + column: "ObjectId", + principalTable: "Lockers", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_RequestLogs_Documents_ObjectId", + table: "RequestLogs", + column: "ObjectId", + principalTable: "Documents", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_RoomLogs_Rooms_ObjectId", + table: "RoomLogs", + column: "ObjectId", + principalTable: "Rooms", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_UserLogs_Users_ObjectId", + table: "UserLogs", + column: "ObjectId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.Designer.cs new file mode 100644 index 00000000..938aeb1a --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.Designer.cs @@ -0,0 +1,1068 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230621135501_AddMoreReason")] + partial class AddMoreReason + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseFolderId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseFolderId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseLockerId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseLockerId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseRoomId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseRoomId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("StaffReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("ImportReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("StaffReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "BaseFolder") + .WithMany() + .HasForeignKey("BaseFolderId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseFolder"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "BaseLocker") + .WithMany() + .HasForeignKey("BaseLockerId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseLocker"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "BaseRoom") + .WithMany() + .HasForeignKey("BaseRoomId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseRoom"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.ImportRequest", "DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.cs b/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.cs new file mode 100644 index 00000000..04890a1d --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230621135501_AddMoreReason.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddMoreReason : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Reason", + table: "ImportRequests", + newName: "StaffReason"); + + migrationBuilder.RenameColumn( + name: "Reason", + table: "Borrows", + newName: "StaffReason"); + + migrationBuilder.AddColumn( + name: "ImportReason", + table: "ImportRequests", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "BorrowReason", + table: "Borrows", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImportReason", + table: "ImportRequests"); + + migrationBuilder.DropColumn( + name: "BorrowReason", + table: "Borrows"); + + migrationBuilder.RenameColumn( + name: "StaffReason", + table: "ImportRequests", + newName: "Reason"); + + migrationBuilder.RenameColumn( + name: "StaffReason", + table: "Borrows", + newName: "Reason"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 9fab7c8c..ba5d9a63 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -104,6 +104,180 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("UserGroups"); }); + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseFolderId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseFolderId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseLockerId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseLockerId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("BaseRoomId") + .HasColumnType("uuid"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BaseRoomId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => { b.Property("Id") @@ -113,19 +287,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ActualReturnTime") .HasColumnType("timestamp without time zone"); + b.Property("BorrowReason") + .IsRequired() + .HasColumnType("text"); + b.Property("BorrowTime") .HasColumnType("timestamp without time zone"); b.Property("BorrowerId") .HasColumnType("uuid"); + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + b.Property("DocumentId") .HasColumnType("uuid"); b.Property("DueTime") .HasColumnType("timestamp without time zone"); - b.Property("Reason") + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("StaffReason") .IsRequired() .HasColumnType("text"); @@ -147,6 +337,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + b.Property("DepartmentId") .HasColumnType("uuid"); @@ -168,6 +364,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ImporterId") .HasColumnType("uuid"); + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + b.Property("Status") .HasColumnType("integer"); @@ -199,6 +404,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Capacity") .HasColumnType("integer"); + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + b.Property("Description") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -206,6 +417,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsAvailable") .HasColumnType("boolean"); + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + b.Property("LockerId") .HasColumnType("uuid"); @@ -224,6 +441,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Folders"); }); + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("ImportReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.Property("StaffReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("ImportRequests"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => { b.Property("Id") @@ -233,6 +495,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Capacity") .HasColumnType("integer"); + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + b.Property("Description") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -240,6 +508,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsAvailable") .HasColumnType("boolean"); + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + b.Property("Name") .IsRequired() .HasMaxLength(64) @@ -258,6 +532,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Lockers"); }); + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Room", b => { b.Property("Id") @@ -267,6 +563,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Capacity") .HasColumnType("integer"); + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + b.Property("DepartmentId") .HasColumnType("uuid"); @@ -277,6 +579,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsAvailable") .HasColumnType("boolean"); + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + b.Property("Name") .IsRequired() .HasMaxLength(64) @@ -289,8 +597,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasAlternateKey("Name"); - b.HasIndex("DepartmentId") - .IsUnique(); + b.HasIndex("DepartmentId"); b.ToTable("Rooms"); }); @@ -467,6 +774,90 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("File"); }); + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "BaseFolder") + .WithMany() + .HasForeignKey("BaseFolderId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseFolder"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "BaseLocker") + .WithMany() + .HasForeignKey("BaseLockerId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseLocker"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "BaseRoom") + .WithMany() + .HasForeignKey("BaseRoomId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BaseRoom"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => { b.HasOne("Domain.Entities.User", "Borrower") @@ -524,6 +915,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Locker"); }); + modelBuilder.Entity("Domain.Entities.Physical.ImportRequest", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.ImportRequest", "DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany() + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Room"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => { b.HasOne("Domain.Entities.Physical.Room", "Room") @@ -535,11 +945,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Room"); }); + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Room", b => { b.HasOne("Domain.Entities.Department", "Department") - .WithOne("Room") - .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -611,7 +1040,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Entities.Department", b => { - b.Navigation("Room"); + b.Navigation("Rooms"); }); modelBuilder.Entity("Domain.Entities.Physical.Folder", b => diff --git a/src/Infrastructure/Services/DateTimeService.cs b/src/Infrastructure/Services/DateTimeService.cs new file mode 100644 index 00000000..bfed60e1 --- /dev/null +++ b/src/Infrastructure/Services/DateTimeService.cs @@ -0,0 +1,8 @@ +using Application.Common.Interfaces; + +namespace Infrastructure.Services; + +public class DateTimeService : IDateTimeProvider +{ + public DateTime DateTimeNow => DateTime.Now; +} \ No newline at end of file diff --git a/src/Infrastructure/Services/PermissionManager.cs b/src/Infrastructure/Services/PermissionManager.cs new file mode 100644 index 00000000..81137b75 --- /dev/null +++ b/src/Infrastructure/Services/PermissionManager.cs @@ -0,0 +1,94 @@ +using System.Configuration; +using Application.Common.Interfaces; +using Application.Common.Models.Operations; +using Domain.Entities; +using Domain.Entities.Physical; +using NodaTime; + +namespace Infrastructure.Services; + +public class PermissionManager : IPermissionManager +{ + private readonly IApplicationDbContext _context; + + public PermissionManager(IApplicationDbContext context) + { + _context = context; + } + + public bool IsGranted(Guid documentId, DocumentOperation operation, params Guid[] userIds) + { + return Array.TrueForAll(userIds, id => _context.Permissions.Any(x => + x.DocumentId == documentId + && x.EmployeeId == id + && x.AllowedOperations.Contains(operation.ToString()))); + } + + public async Task GrantAsync(Document document, DocumentOperation operation, User[] users, DateTime expiryDate, CancellationToken cancellationToken) + { + foreach (var user in users) + { + var existedPermission = + _context.Permissions.FirstOrDefault(x => x.DocumentId == document.Id && x.EmployeeId == user.Id); + + if (existedPermission is not null) + { + var operations = existedPermission.AllowedOperations.Split(","); + if (operations.Contains(operation.ToString())) + { + continue; + } + + var x = new CommaDelimitedStringCollection + { + existedPermission.AllowedOperations, + operation.ToString() + }; + existedPermission.AllowedOperations = x.ToString(); + existedPermission.ExpiryDateTime = LocalDateTime.FromDateTime(expiryDate); + _context.Permissions.Update(existedPermission); + } + else + { + existedPermission = new Permission() + { + DocumentId = document.Id, + EmployeeId = user.Id, + Document = document, + Employee = user, + AllowedOperations = operation.ToString(), + }; + existedPermission.ExpiryDateTime = LocalDateTime.FromDateTime(expiryDate); + await _context.Permissions.AddAsync(existedPermission, cancellationToken); + } + } + + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task RevokeAsync(Guid documentId, DocumentOperation operation, Guid[] userIds, CancellationToken cancellationToken) + { + foreach (var userId in userIds) + { + var existedPermission = + _context.Permissions.FirstOrDefault(x => x.DocumentId == documentId && x.EmployeeId == userId); + if (existedPermission is null) continue; + var operations = existedPermission.AllowedOperations.Split(","); + if (!operations.Contains(operation.ToString())) continue; + + var x = new CommaDelimitedStringCollection(); + x.AddRange(operations); + x.Remove(operation.ToString()); + if (x.Count == 0 || existedPermission.ExpiryDateTime < LocalDateTime.FromDateTime(DateTime.Now)) + { + _context.Permissions.Remove(existedPermission); + } + else + { + existedPermission.AllowedOperations = x.ToString(); + _context.Permissions.Update(existedPermission); + } + } + await _context.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/BaseClassFixture.cs b/tests/Application.Tests.Integration/BaseClassFixture.cs index eaa0afa5..f4644e5e 100644 --- a/tests/Application.Tests.Integration/BaseClassFixture.cs +++ b/tests/Application.Tests.Integration/BaseClassFixture.cs @@ -242,7 +242,7 @@ protected static Borrow CreateBorrowRequest(User borrower, Document document, Bo Id = Guid.NewGuid(), Borrower = borrower, Document = document, - Reason = "something something", + BorrowReason = "something something", Status = status, BorrowTime = LocalDateTime.FromDateTime(DateTime.Now), DueTime = LocalDateTime.FromDateTime(DateTime.Now + TimeSpan.FromDays(1)) diff --git a/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs index 87a40fd6..09f8d7bd 100644 --- a/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs +++ b/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs @@ -1,4 +1,4 @@ -using Application.Borrows.Commands; +using Application.Borrows.Commands; using Application.Common.Exceptions; using Application.Identity; using Domain.Entities.Physical; @@ -30,7 +30,7 @@ public async Task ShouldApproveRequest_WhenRequestIsValid() await AddAsync(request); - var command = new ApproveBorrowRequest.Command() + var command = new ApproveOrRejectBorrowRequest.Command() { BorrowId = request.Id, }; @@ -51,7 +51,7 @@ public async Task ShouldApproveRequest_WhenRequestIsValid() public async Task ShouldThrowKeyNotFoundException_WhenRequestDoesNotExist() { // Arrange - var command = new ApproveBorrowRequest.Command() + var command = new ApproveOrRejectBorrowRequest.Command() { BorrowId = Guid.NewGuid(), }; @@ -78,7 +78,7 @@ public async Task ShouldThrowConflictException_WhenDocumentIsLost() await AddAsync(request); - var command = new ApproveBorrowRequest.Command() + var command = new ApproveOrRejectBorrowRequest.Command() { BorrowId = request.Id, }; @@ -108,7 +108,7 @@ public async Task ShouldThrowConflictException_WhenRequestStatusIsNotPendingAndR await AddAsync(request); - var command = new ApproveBorrowRequest.Command() + var command = new ApproveOrRejectBorrowRequest.Command() { BorrowId = request.Id, }; @@ -146,7 +146,7 @@ public async Task ShouldThrowConflictException_WhenRequestTimespanOverlapAnAppro await context.AddAsync(request2); await context.SaveChangesAsync(); - var command = new ApproveBorrowRequest.Command() + var command = new ApproveOrRejectBorrowRequest.Command() { BorrowId = request2.Id, }; diff --git a/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs index b595d20f..ee5a8708 100644 --- a/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs +++ b/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs @@ -42,7 +42,7 @@ public async Task ShouldCreateBorrowRequest_WhenDetailsAreValid() { BorrowerId = user.Id, DocumentId = document.Id, - Reason = "Example", + BorrowReason = "Example", BorrowFrom = DateTime.Now.Add(TimeSpan.FromHours(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(1)), }; @@ -53,7 +53,7 @@ public async Task ShouldCreateBorrowRequest_WhenDetailsAreValid() // Assert result.DocumentId.Should().Be(command.DocumentId); result.BorrowerId.Should().Be(command.BorrowerId); - result.Reason.Should().Be(command.Reason); + result.BorrowReason.Should().Be(command.BorrowReason); result.BorrowTime.Should().Be(command.BorrowFrom); result.DueTime.Should().Be(command.BorrowTo); result.Status.Should().Be(BorrowRequestStatus.Pending.ToString()); @@ -79,7 +79,7 @@ public async Task ShouldThrowKeyNotFoundException_WhenUserDoesNotExist() DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -111,7 +111,7 @@ public async Task ShouldThrowConflictException_WhenUserIsNotActive() DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -144,7 +144,7 @@ public async Task ShouldThrowConflictException_WhenUserIsNotActivated() DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -172,7 +172,7 @@ public async Task ShouldThrowKeyNotFoundException_WhenDocumentDoesNotExist() DocumentId = Guid.NewGuid(), BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -203,7 +203,7 @@ public async Task ShouldConflictException_WhenDocumentIsNotAvailable() DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -244,7 +244,7 @@ public async Task ShouldConflictException_WhenUserAndDocumentDoesNotBelongToTheS DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -287,7 +287,7 @@ public async Task ShouldThrowConflictException_WhenRequestWithSameUserAndDocumen DocumentId = document.Id, BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act @@ -334,7 +334,7 @@ public async Task ShouldThrowConflictException_WhenARequestIsMadeWhileDocumentIs DocumentId = document.Id, BorrowFrom = DateTime.Now.AddHours(1), BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), - Reason = "Example", + BorrowReason = "Example", }; // Act diff --git a/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs index f00d3e25..841a1f04 100644 --- a/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs +++ b/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs @@ -32,7 +32,7 @@ public async Task ShouldUpdateBorrow_WhenDetailsAreValid() var command = new UpdateBorrow.Command() { - Reason = "Example Update", + BorrowReason = "Example Update", BorrowFrom = DateTime.Now.AddDays(3), BorrowTo = DateTime.Now.AddDays(12), BorrowId = borrow.Id, @@ -42,7 +42,7 @@ public async Task ShouldUpdateBorrow_WhenDetailsAreValid() var result = await SendAsync(command); // Assert - result.Reason.Should().Be(command.Reason); + result.BorrowReason.Should().Be(command.BorrowReason); result.BorrowTime.Should().Be(command.BorrowFrom); result.DueTime.Should().Be(command.BorrowTo); @@ -58,7 +58,7 @@ public async Task ShouldThrowKeyNotFoundException_WhenRequestDoesNotExist() // Arrange var command = new UpdateBorrow.Command() { - Reason = "adsda", + BorrowReason = "adsda", BorrowFrom = DateTime.Now.AddHours(1), BorrowTo = DateTime.Now.AddHours(2), BorrowId = Guid.NewGuid(), @@ -86,7 +86,7 @@ public async Task ShouldThrowConflictException_WhenRequestStatusIsNotPending() var command = new UpdateBorrow.Command() { - Reason = "Example Update", + BorrowReason = "Example Update", BorrowFrom = DateTime.Now.AddDays(3), BorrowTo = DateTime.Now.AddDays(12), BorrowId = borrow.Id, @@ -120,7 +120,7 @@ public async Task ShouldThrowConflictException_WhenDocumentIsLost() var command = new UpdateBorrow.Command() { - Reason = "Example Update", + BorrowReason = "Example Update", BorrowFrom = DateTime.Now.AddDays(3), BorrowTo = DateTime.Now.AddDays(12), BorrowId = borrow.Id, @@ -163,7 +163,7 @@ public async Task ShouldThrowConflictException_WhenRequestTimespanOverlapAnAppro var command = new UpdateBorrow.Command() { - Reason = "Example Update", + BorrowReason = "Example Update", BorrowFrom = DateTime.Now.AddDays(5), BorrowTo = DateTime.Now.AddDays(13), BorrowId = borrow1.Id, diff --git a/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs deleted file mode 100644 index cf2628d7..00000000 --- a/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Application.Common.Exceptions; -using Application.Folders.Commands; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Folders.Commands; - -public class DisableFolderTests : BaseClassFixture -{ - public DisableFolderTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldDisableFolder_WhenFolderHaveNoDocument() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var disableFolderCommand = new DisableFolder.Command() - { - FolderId = folder.Id - }; - - // Act - var disabledFolder = await SendAsync(disableFolderCommand); - - // Assert - disabledFolder.IsAvailable.Should().BeFalse(); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenFolderDoesNotExist() - { - // Arrange - var disableFolderCommand = new DisableFolder.Command() - { - FolderId = Guid.NewGuid() - }; - - // Act - var result = async () => await SendAsync(disableFolderCommand); - - // Assert - await result.Should().ThrowAsync() - .WithMessage("Folder does not exist."); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenFolderIsAlreadyDisabled() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - folder.IsAvailable = false; - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - await AddAsync(room); - var disableFolderCommand = new DisableFolder.Command() - { - FolderId = folder.Id - }; - - // Act - var result = async () => await SendAsync(disableFolderCommand); - - // Assert - await result.Should().ThrowAsync() - .WithMessage("Folder has already been disabled."); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenFolderHasDocuments() - { - // Arrange - var department = CreateDepartment(); - var document = CreateNDocuments(1).First(); - var folder = CreateFolder(document); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - await AddAsync(room); - var disableFolderCommand = new DisableFolder.Command() - { - FolderId = folder.Id - }; - - // Act - var result = async () => await SendAsync(disableFolderCommand); - - // Assert - await result.Should().ThrowAsync() - .WithMessage("Folder cannot be disabled because it contains documents."); - - // Cleanup - Remove(document); - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Commands/EnableFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/EnableFolderTests.cs deleted file mode 100644 index 62f0d165..00000000 --- a/tests/Application.Tests.Integration/Folders/Commands/EnableFolderTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Application.Common.Exceptions; -using Application.Folders.Commands; -using Domain.Entities; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Folders.Commands; - -public class EnableFolderTests : BaseClassFixture -{ - public EnableFolderTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldEnableFolder_WhenThatFolderExistsAndIsDisabled() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - folder.IsAvailable = false; - await AddAsync(room); - - var command = new EnableFolder.Command() - { - FolderId = folder.Id, - }; - - // Act - var result = await SendAsync(command); - - // Assert - result.IsAvailable.Should().BeTrue(); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenThatFolderDoesNotExist() - { - // Arrange - var command = new EnableFolder.Command() - { - FolderId = Guid.NewGuid(), - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Folder does not exist."); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenFolderIsAlreadyAvailable() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - folder.IsAvailable = true; - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var command = new EnableFolder.Command() - { - FolderId = folder.Id, - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Folder has already been enabled."); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/DisableLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/DisableLockerTests.cs deleted file mode 100644 index 5cf76d23..00000000 --- a/tests/Application.Tests.Integration/Lockers/Commands/DisableLockerTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Application.Common.Exceptions; -using Application.Lockers.Commands; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Lockers.Commands; - -public class DisableLockerTests : BaseClassFixture -{ - public DisableLockerTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldDisableLocker_WhenLockerExistsAndIsAvailable() - { - // Arrange - var department = CreateDepartment(); - var locker = CreateLocker(); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var disableLockerCommand = new DisableLocker.Command() - { - LockerId = locker.Id, - }; - - // Act - var result = await SendAsync(disableLockerCommand); - - // Assert - result.Name.Should().Be(locker.Name); - result.Description.Should().Be(locker.Description); - result.Capacity.Should().Be(locker.Capacity); - result.IsAvailable.Should().BeFalse(); - result.NumberOfFolders.Should().Be(locker.NumberOfFolders); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() - { - // Arrange - var disableLockerCommand = new DisableLocker.Command() - { - LockerId = Guid.NewGuid(), - }; - - // Act - var action = async () => await SendAsync(disableLockerCommand); - - // Assert - await action.Should() - .ThrowAsync() - .WithMessage("Locker does not exist."); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenLockerIsAlreadyDisabled() - { - // Arrange - var department = CreateDepartment(); - var locker = CreateLocker(); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var disableLockerCommand = new DisableLocker.Command() - { - LockerId = locker.Id, - }; - - // Act - await SendAsync(disableLockerCommand); - var action = async () => await SendAsync(disableLockerCommand); - - // Assert - await action.Should().ThrowAsync().WithMessage("Locker has already been disabled."); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/EnableLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/EnableLockerTests.cs deleted file mode 100644 index b4ec3df7..00000000 --- a/tests/Application.Tests.Integration/Lockers/Commands/EnableLockerTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Application.Common.Exceptions; -using Application.Lockers.Commands; -using Domain.Entities; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Lockers.Commands; - -public class EnableLockerTests : BaseClassFixture -{ - public EnableLockerTests(CustomApiFactory apiFactory) : base(apiFactory) - { - - } - - [Fact] - public async Task ShouldEnableLocker_WhenLockerExistsAndIsNotAvailable() - { - // Arrange - var department = CreateDepartment(); - var locker = CreateLocker(); - locker.IsAvailable = false; - var room = CreateRoom(department, locker); - await AddAsync(room); - - // Act - var command = new EnableLocker.Command() - { - LockerId = locker.Id, - }; - - var result = await SendAsync(command); - - // Assert - result.IsAvailable.Should().BeTrue(); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() - { - // Arrange - var command = new EnableLocker.Command() - { - LockerId = Guid.NewGuid(), - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should() - .ThrowAsync() - .WithMessage("Locker does not exist."); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenLockerIsAlreadyEnabled() - { - // Arrange - var department = CreateDepartment(); - var locker = CreateLocker(); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var enableLockerCommand = new EnableLocker.Command() - { - LockerId = locker.Id, - }; - - // Act - var action = async () => await SendAsync(enableLockerCommand); - - // Assert - await action.Should() - .ThrowAsync() - .WithMessage("Locker has already been enabled."); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } -} diff --git a/tests/Application.Tests.Integration/Rooms/Commands/DisableRoomTests.cs b/tests/Application.Tests.Integration/Rooms/Commands/DisableRoomTests.cs deleted file mode 100644 index 39badcb8..00000000 --- a/tests/Application.Tests.Integration/Rooms/Commands/DisableRoomTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Application.Common.Exceptions; -using Application.Rooms.Commands; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Rooms.Commands; - -public class DisableRoomTests : BaseClassFixture -{ - public DisableRoomTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldDisableRoom_WhenRoomHaveNoDocument() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - await AddAsync(room); - - var disableRoomCommand = new DisableRoom.Command() - { - RoomId = room.Id - }; - - // Act - var result = await SendAsync(disableRoomCommand); - - // Assert - var folderResult = await FindAsync(folder.Id); - var lockerResult = await FindAsync(locker.Id); - - result.IsAvailable.Should().BeFalse(); - folderResult.IsAvailable.Should().BeFalse(); - lockerResult.IsAvailable.Should().BeFalse(); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() - { - // Arrange - var disableRoomCommand = new DisableRoom.Command() - { - RoomId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(disableRoomCommand); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room does not exist."); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenRoomIsNotEmptyOfDocuments() - { - // Arrange - var department = CreateDepartment(); - var documents = CreateNDocuments(1); - var folder = CreateFolder(documents); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - - await AddAsync(room); - - var disableRoomCommand = new DisableRoom.Command() - { - RoomId = room.Id - }; - - // Act - var action = async () => await SendAsync(disableRoomCommand); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room cannot be disabled because it contains documents."); - - // Cleanup - Remove(documents.First()); - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenRoomIsNotAvailable() - { - // Arrange - var department = CreateDepartment(); - var room = CreateRoom(department); - room.IsAvailable = false; - await AddAsync(room); - - var command = new DisableRoom.Command() - { - RoomId = room.Id - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room have already been disabled."); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Commands/EnableRoomTests.cs b/tests/Application.Tests.Integration/Rooms/Commands/EnableRoomTests.cs deleted file mode 100644 index fa07d0b8..00000000 --- a/tests/Application.Tests.Integration/Rooms/Commands/EnableRoomTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Application.Common.Exceptions; -using Application.Rooms.Commands; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Rooms.Commands; - -public class EnableRoomTests : BaseClassFixture -{ - public EnableRoomTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldEnableRoom_WhenRoomExistsAndIsDisabled() - { - // Arrange - var department = CreateDepartment(); - var folder = CreateFolder(); - var locker = CreateLocker(folder); - var room = CreateRoom(department, locker); - folder.IsAvailable = false; - locker.IsAvailable = false; - room.IsAvailable = false; - await AddAsync(room); - - var command = new EnableRoom.Command() - { - RoomId = room.Id - }; - - // Act - var result = await SendAsync(command); - - // Assert - var folderResult = await FindAsync(folder.Id); - var lockerResult = await FindAsync(locker.Id); - - result.IsAvailable.Should().BeTrue(); - folderResult!.IsAvailable.Should().BeFalse(); - lockerResult!.IsAvailable.Should().BeFalse(); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() - { - // Arrange - var command = new EnableRoom.Command() - { - RoomId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room does not exist."); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenRoomIsAlreadyAvailable() - { - // Arrange - var department = CreateDepartment(); - var room = CreateRoom(department); - room.IsAvailable = true; - await AddAsync(room); - - var command = new EnableRoom.Command() - { - RoomId = room.Id - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room has already been enabled."); - - // Cleanup - Remove(room); - Remove(await FindAsync(department.Id)); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffTests.cs b/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffTests.cs deleted file mode 100644 index 92df91b8..00000000 --- a/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Application.Identity; -using Application.Staffs.Commands; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Staffs.Commands; - -public class RemoveStaffTests : BaseClassFixture -{ - public RemoveStaffTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldRemoveStaff_WhenStaffIdIsValid() - { - // Arrange - var department = CreateDepartment(); - var user = CreateUser(IdentityData.Roles.Admin, "123456"); - var room = CreateRoom(department); - var staff = CreateStaff(user, room); - await AddAsync(staff); - - var command = new RemoveStaff.Command() - { - StaffId = staff.Id - }; - - // Act - await SendAsync(command); - - // Assert - var result = await FindAsync(staff.Id); - result.Should().BeNull(); - - // Cleanup - Remove(await FindAsync(room.Id)); - Remove(user); - Remove(await FindAsync(department.Id)); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenStaffDoesNotExist() - { - // Arrange - var command = new RemoveStaff.Command() - { - StaffId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Staff does not exist."); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Staffs/Queries/GetAllStaffsPaginatedTests.cs b/tests/Application.Tests.Integration/Staffs/Queries/GetAllStaffsPaginatedTests.cs index 4c0547ef..8033323b 100644 --- a/tests/Application.Tests.Integration/Staffs/Queries/GetAllStaffsPaginatedTests.cs +++ b/tests/Application.Tests.Integration/Staffs/Queries/GetAllStaffsPaginatedTests.cs @@ -66,8 +66,7 @@ public async Task ShouldReturnASpecificStaff() // Assert result.TotalCount.Should().Be(1); result.Items.First().Should() - .BeEquivalentTo(_mapper.Map(staff1), - config => config.Excluding(x => x.User.Created)); + .BeEquivalentTo(_mapper.Map(staff1)); // Cleanup Remove(staff2); diff --git a/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs index 123e2542..b0da147c 100644 --- a/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs +++ b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs @@ -31,7 +31,7 @@ public async Task ShouldReturnStaff_WhenRoomHaveStaff() var staff = CreateStaff(user, room); await AddAsync(staff); - var query = new GetStaffByRoom.Query() + var query = new GetStaffByRoomId.Query() { RoomId = room.Id }; @@ -57,7 +57,7 @@ public async Task ShouldReturnStaff_WhenRoomHaveStaff() public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() { // Arrange - var query = new GetStaffByRoom.Query() + var query = new GetStaffByRoomId.Query() { RoomId = Guid.NewGuid() }; @@ -78,7 +78,7 @@ public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotHaveStaff() var room = CreateRoom(department); await AddAsync(room); - var query = new GetStaffByRoom.Query() + var query = new GetStaffByRoomId.Query() { RoomId = room.Id }; diff --git a/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs b/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs index 49db0eca..7f6a4ac8 100644 --- a/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs +++ b/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs @@ -2,6 +2,7 @@ using Application.Common.Mappings; using Application.Common.Models.Dtos; using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Dtos.ImportDocument; using Application.Common.Models.Dtos.Physical; using Application.Users.Queries; using AutoMapper; @@ -48,6 +49,9 @@ public void ShouldHaveValidConfiguration() [InlineData(typeof(FileEntity), typeof(FileDto))] [InlineData(typeof(Entry), typeof(EntryDto))] [InlineData(typeof(UserGroup), typeof(UserGroupDto))] + [InlineData(typeof(User), typeof(IssuerDto))] + [InlineData(typeof(Document), typeof(IssuedDocumentDto))] + [InlineData(typeof(ImportRequest), typeof(ImportRequestDto))] public void ShouldSupportMappingFromSourceToDestination(Type source, Type destination) { // Arrange