diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..cf9e4655 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "dev", main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "dev" ] + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/versioning.yml b/.github/workflows/versioning.yml new file mode 100644 index 00000000..2928e706 --- /dev/null +++ b/.github/workflows/versioning.yml @@ -0,0 +1,21 @@ +name: Bump version +on: + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} diff --git a/Dockerfile b/Dockerfile index 785a5d1d..00b89364 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ CMD ["dotnet", "test", "--logger:trx"] # Publish stage FROM build AS publish WORKDIR /app/src/Api -RUN dotnet publish -c Release -o /app/publish +RUN dotnet publish -c Release --no-restore -o /app/publish # Final stage FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal AS runtime diff --git a/docker-compose.test.yml b/docker-compose.test.yml index d7d44b92..a1e8f565 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -8,8 +8,8 @@ services: ports: - '8888:80' environment: - - ASPNETCORE_ENVIRONMENT=Development - - PROFILE_DatabaseSettings__ConnectionString=Server=database;Port=5432;Database=mytestdb;User ID=profiletester;Password=supasupasecured; + - ASPNETCORE_ENVIRONMENT=Testing + - PROFILE_DatabaseSettings__ConnectionString=Server=database;Port=5432;Database=mytestdb;User ID=profiletester;Password=supasupasecured;Include Error Detail=true depends_on: database: condition: service_started diff --git a/docker-compose.yml b/docker-compose.yml index f478e29b..295d1679 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,15 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - PROFILE_DatabaseSettings__ConnectionString=Server=database;Port=5432;Database=mydb;User ID=profiletest;Password=supasecured; + - PROFILE_JweSettings__SigningKeyId=4bd28be8eac5414fb01c5cbe343b50144bd28be8eac5414fb01c5cbe343b50144bd28be8eac5414fb01c5cbe343b50144bd28be8eac5414fb01c5cbe343b50144bd28be8eac5414fb01c5cbe343b5014 + - PROFILE_JweSettings__EncryptionKeyId=4bd28be8eac5414fb01c5cbe343b5014 + - PROFILE_JweSettings__TokenLifetime=00:20:00 + - PROFILE_JweSettings__RefreshTokenLifetimeInDays=3 + - PROFILE_MailSettings__ClientUrl=https://send.api.mailtrap.io/api/send + - PROFILE_MailSettings__Token=745f040659edff0ce87b545567da72d2 + - PROFILE_MailSettings__SenderName=ProFile + - PROFILE_MailSettings__SenderEmail=profile@ezarp.dev + - PROFILE_MailSettings__TemplateUuid=9d6a8f25-65e9-4819-be7d-106ce077acf1 depends_on: database: condition: service_started @@ -20,4 +29,9 @@ services: - POSTGRES_PASSWORD=supasecured - POSTGRES_DB=mydb ports: - - '5432:5432' \ No newline at end of file + - '5432:5432' + volumes: + - pg_data:/var/lib/postgresql/data + +volumes: + pg_data: \ No newline at end of file diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index 09720f26..d45ec98c 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -4,6 +4,7 @@ net6.0 enable enable + true @@ -13,6 +14,8 @@ + + diff --git a/src/Api/Common/CORSPolicy.cs b/src/Api/Common/CORSPolicy.cs new file mode 100644 index 00000000..a65f0d1b --- /dev/null +++ b/src/Api/Common/CORSPolicy.cs @@ -0,0 +1,7 @@ +namespace Api.Common; + +public static class CORSPolicy +{ + public static string Development = "devEnvironment"; + public static string Production = "prodEnvironment"; +} \ No newline at end of file diff --git a/src/Api/ConfigureServices.cs b/src/Api/ConfigureServices.cs index 9420dab1..390e032b 100644 --- a/src/Api/ConfigureServices.cs +++ b/src/Api/ConfigureServices.cs @@ -1,15 +1,24 @@ +using System.Reflection; +using Api.Common; using Api.Middlewares; using Api.Policies; +using Api.Services; +using Application.Common.Interfaces; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.OpenApi.Models; namespace Api; public static class ConfigureServices { - public static IServiceCollection AddApiServices(this IServiceCollection services) + public static IServiceCollection AddApiServices(this IServiceCollection services, IConfiguration configuration) { + // TODO: Move this away in the future + var frontendBaseUrl = configuration.GetValue("BASE_FRONTEND_URL") ?? "http://localhost"; + // Register services services.AddServices(); + services.AddHostedService(); services.AddControllers(opt => opt.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()))); @@ -18,11 +27,20 @@ public static IServiceCollection AddApiServices(this IServiceCollection services services.AddCors(options => { - options.AddPolicy("AllowAllOrigins", builder => + options.AddPolicy(CORSPolicy.Development, builder => + { + builder.SetIsOriginAllowed(_ => true); + builder.AllowAnyHeader(); + builder.AllowAnyMethod(); + builder.AllowCredentials(); + }); + + options.AddPolicy(CORSPolicy.Production, builder => { - builder.AllowAnyOrigin(); + builder.WithOrigins(frontendBaseUrl); builder.AllowAnyHeader(); builder.AllowAnyMethod(); + builder.AllowCredentials(); }); }); @@ -30,7 +48,18 @@ public static IServiceCollection AddApiServices(this IServiceCollection services // For swagger services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "ProFile API", + Description = "An ASP.NET Core Web API for managing documents", + }); + + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + }); return services; } @@ -39,6 +68,7 @@ private static IServiceCollection AddServices(this IServiceCollection services) { // In order for ExceptionMiddleware to work services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/Api/Controllers/AuthController.cs b/src/Api/Controllers/AuthController.cs index b0941458..c4e980c8 100644 --- a/src/Api/Controllers/AuthController.cs +++ b/src/Api/Controllers/AuthController.cs @@ -1,9 +1,13 @@ using System.IdentityModel.Tokens.Jwt; -using Api.Controllers.Payload.Requests; +using System.Security.Authentication; +using Api.Controllers.Payload.Requests.Auth; using Api.Controllers.Payload.Responses; +using Application.Common.Extensions.Logging; using Application.Common.Interfaces; +using Application.Common.Logging; using Application.Common.Models; using Application.Common.Models.Dtos; +using Application.Users.Queries; using Domain.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,92 +20,152 @@ namespace Api.Controllers; public class AuthController : ControllerBase { private readonly IIdentityService _identityService; + private readonly ILogger _logger; + private readonly IDateTimeProvider _dateTimeProvider; - public AuthController(IIdentityService identityService) + public AuthController(IIdentityService identityService, ILogger logger, IDateTimeProvider dateTimeProvider) { _identityService = identityService; + _logger = logger; + _dateTimeProvider = dateTimeProvider; } + /// + /// Login + /// + /// Login credentials + /// A LoginResult indicating the result of logging in [AllowAnonymous] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task>> Login([FromBody] LoginModel loginModel) + public async Task Login([FromBody] LoginModel loginModel) { var result = await _identityService.LoginAsync(loginModel.Email, loginModel.Password); - - SetRefreshToken(result.AuthResult.RefreshToken); - SetJweToken(result.AuthResult.Token); - var loginResult = new LoginResult() - { - Id = result.UserCredentials.Id, - Username = result.UserCredentials.Username, - Email = result.UserCredentials.Email, - Department = result.UserCredentials.Department, - Position = result.UserCredentials.Position, - Role = result.UserCredentials.Role, - FirstName = result.UserCredentials.FirstName, - LastName = result.UserCredentials.LastName, - }; - - return Ok(Result.Succeed(loginResult)); - } + return result.Match(loginSuccess => + { + SetRefreshToken(loginSuccess.AuthResult.RefreshToken); + SetJweToken(loginSuccess.AuthResult.Token, loginSuccess.AuthResult.RefreshToken); - [Authorize] + var loginResult = new LoginResult() + { + Id = loginSuccess.UserCredentials.Id, + Username = loginSuccess.UserCredentials.Username, + Email = loginSuccess.UserCredentials.Email, + Department = loginSuccess.UserCredentials.Department, + Position = loginSuccess.UserCredentials.Position, + Role = loginSuccess.UserCredentials.Role, + FirstName = loginSuccess.UserCredentials.FirstName, + LastName = loginSuccess.UserCredentials.LastName, + }; + + var dateTimeNow = _dateTimeProvider.DateTimeNow; + using (Logging.PushProperties("Login", loginResult.Id, loginResult.Id)) + { + _logger.LogLogin(loginResult.Username, dateTimeNow.ToString("yyyy-MM-dd HH:mm:ss")); + } + return Ok(Result.Succeed(loginResult)); + }, + token => + { + var r = new Result + { + Data = new NotActivatedLoginResult() + { + Token = token, + } + }; + return Unauthorized(r); + }); + + } + + /// + /// Refresh session and token + /// + /// An IActionResult indicating the result of refreshing token [HttpPost] - public async Task Logout() + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task Refresh() { var refreshToken = Request.Cookies[nameof(RefreshToken)]; var jweToken = Request.Cookies["JweToken"]; - var loggedOut = await _identityService.LogoutAsync(jweToken!, refreshToken!); - - if (!loggedOut) return Ok(); - - RemoveJweToken(); - RemoveRefreshToken(); + try + { + var authResult = await _identityService.RefreshTokenAsync(jweToken!, refreshToken!); + + SetRefreshToken(authResult.RefreshToken); + SetJweToken(authResult.Token, authResult.RefreshToken); + } + catch (AuthenticationException) + { + RemoveJweToken(); + RemoveRefreshToken(); + throw; + } return Ok(); } - + + /// + /// Validate current user + /// + /// An IActionResult indicating the result of validating the user [Authorize] [HttpPost] - public async Task Refresh() + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public IActionResult Validate() + { + return Ok(); + } + + /// + /// Logout of the system + /// + /// An IActionResult indicating the result of logging out of the system + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task Logout() { var refreshToken = Request.Cookies[nameof(RefreshToken)]; var jweToken = Request.Cookies["JweToken"]; - var authResult = await _identityService.RefreshTokenAsync(jweToken!, refreshToken!); - - SetRefreshToken(authResult.RefreshToken); - SetJweToken(authResult.Token); + RemoveJweToken(); + RemoveRefreshToken(); + await _identityService.LogoutAsync(jweToken!, refreshToken!); + return Ok(); } - [Authorize] [HttpPost] - public async Task Validate() + public async Task ResetPassword([FromBody] ResetPasswordRequest request) { - var refreshToken = Request.Cookies[nameof(RefreshToken)]; - var jweToken = Request.Cookies["JweToken"]; + if (string.IsNullOrEmpty(request.NewPassword)) + { + return BadRequest("Password cannot be empty."); + } - var validated = await _identityService.Validate(jweToken!, refreshToken!); - - if (validated) + if (!request.NewPassword.Equals(request.ConfirmPassword)) { - return Ok(); + return BadRequest("Confirm password must match with new password."); } - return Unauthorized(); + await _identityService.ResetPassword(request.Token, request.NewPassword); + return Ok(); } - - private void SetJweToken(SecurityToken jweToken) + + private void SetJweToken(SecurityToken jweToken, RefreshTokenDto newRefreshToken) { var cookieOptions = new CookieOptions { HttpOnly = true, + Expires = newRefreshToken.ExpiryDateTime }; var handler = new JwtSecurityTokenHandler(); Response.Cookies.Append("JweToken", handler.WriteToken(jweToken), cookieOptions); diff --git a/src/Api/Controllers/BinController.cs b/src/Api/Controllers/BinController.cs new file mode 100644 index 00000000..6b987051 --- /dev/null +++ b/src/Api/Controllers/BinController.cs @@ -0,0 +1,138 @@ +using Api.Controllers.Payload.Requests.BinEntries; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using Application.Entries.Queries; +using Application.Entries.Commands; +using Application.Identity; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +public class BinController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + + public BinController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Move an entry to bin + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost("entries")] + public async Task>> MoveEntryToBin( + [FromQuery] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + + var command = new MoveEntryToBin.Command() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Restore an entry from bin + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPut("entries/{entryId:guid}/restore")] + public async Task>> RestoreBinEntry( + [FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + + var command = new RestoreBinEntry.Command() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + + /// + /// Remove an entry from bin + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpDelete("entries/{entryId:guid}")] + public async Task>> DeleteBinEntry( + [FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + + var command = new DeleteBinEntry.Command() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + + /// + /// Get entry in the bin by Id + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries/{entryId:guid}")] + public async Task>> GetBinEntryById( + [FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + + var command = new GetBinEntryById.Query() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Get all entry in the bin + /// + /// + /// a paginated list of EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries")] + public async Task>>> GetAllBinEntriesPaginated( + [FromQuery] GetAllBinEntriesPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + + var command = new GetAllBinEntriesPaginated.Query() + { + CurrentUser = currentUser, + EntryPath = queryParameters.EntryPath, + Page = queryParameters.Page, + Size = queryParameters.Size, + SearchTerm = queryParameters.SearchTerm, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } +} diff --git a/src/Api/Controllers/BorrowsController.cs b/src/Api/Controllers/BorrowsController.cs new file mode 100644 index 00000000..43379604 --- /dev/null +++ b/src/Api/Controllers/BorrowsController.cs @@ -0,0 +1,297 @@ +using Api.Controllers.Payload.Requests; +using Api.Controllers.Payload.Requests.Borrows; +using Application.Borrows.Commands; +using Application.Borrows.Queries; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using Application.Loggings.Queries; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +[Route("api/v1/documents/[controller]")] +public class BorrowsController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + public BorrowsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// + /// Borrow a document request + /// + /// Borrow document details + /// A BorrowDto of the requested borrow + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> BorrowDocument( + [FromBody] BorrowDocumentRequest request) + { + var borrowerId = _currentUserService.GetCurrentUser().Id; + var command = new BorrowDocument.Command() + { + BorrowerId = borrowerId, + DocumentId = request.DocumentId, + BorrowFrom = request.BorrowFrom, + BorrowTo = request.BorrowTo, + BorrowReason = request.Reason, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Get a borrow request by id + /// + /// A BorrowDto of the retrieved borrow + [RequiresRole(IdentityData.Roles.Admin ,IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpGet("{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> GetById( + [FromRoute] Guid borrowId) + { + var user = _currentUserService.GetCurrentUser(); + var command = new GetBorrowRequestById.Query() + { + BorrowId = borrowId, + User = user, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// + /// + /// + /// + [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>>> GetAllRequestsPaginated( + [FromQuery] GetAllBorrowRequestsPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new GetAllBorrowRequestsPaginated.Query() + { + CurrentUser = currentUser, + RoomId = queryParameters.RoomId, + EmployeeId = queryParameters.EmployeeId, + DocumentId = queryParameters.DocumentId, + Statuses = queryParameters.Statuses, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } + + /// + /// 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)] + [HttpPut("staffs/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> ApproveOrRejectRequest( + [FromRoute] Guid borrowId, + [FromBody] ApproveOrRejectBorrowRequestRequest request) + { + 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)); + } + + /// + /// Check out a borrow request + /// + /// Id of the borrow request to be checked out + /// A BorrowDto of the checked out borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("checkout/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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); + return Ok(Result.Succeed(result)); + } + + /// + /// Return document + /// + /// Id of the document to be returned + /// A BorrowDto of the returned document borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("return/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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); + return Ok(Result.Succeed(result)); + } + + /// + /// Update borrow request + /// + /// Id of the borrow request to be updated + /// Update borrow details + /// A BorrowDto of the updated borrow request + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPut("{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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, + BorrowReason = request.Reason, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Cancel borrow request + /// + /// Id of the borrow request to be cancelled + /// A BorrowDto of the cancelled borrow request + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost("cancel/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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)); + } + + /// + /// Report lost document + /// + /// Id of the borrow request to be reported + /// A BorrowDto of the cancelled borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("lost/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> LostReport([FromRoute] Guid borrowId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new ReportLostDocument.Command() + { + CurrentUser = currentUser, + BorrowId = borrowId, + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Report found document + /// + /// Id of the borrow request to be reported + /// + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("found/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> FoundReport([FromRoute] Guid borrowId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new ReportFoundDocument.Command() + { + CurrentUser = currentUser, + 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/DashboardController.cs b/src/Api/Controllers/DashboardController.cs new file mode 100644 index 00000000..346c786f --- /dev/null +++ b/src/Api/Controllers/DashboardController.cs @@ -0,0 +1,62 @@ +using Api.Controllers.Payload.Requests.Dashboard; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.DashBoard; +using Application.Dashboards.Queries; +using Application.Identity; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +public class DashboardController : ApiControllerBase +{ + [RequiresRole(IdentityData.Roles.Admin)] + [HttpPost("import-documents")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task>>> GetImportDocuments( + [FromBody] GetImportedDocumentsMetricsRequest request) + { + var query = new GetImportDocuments.Query() + { + StartDate = request.StartDate, + EndDate = request.EndDate + }; + + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + [RequiresRole(IdentityData.Roles.Admin)] + [HttpPost("logins")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task>> GetLoggedInUsers( + [FromBody] DateTime date) + { + var query = new GetLoggedInUser.Query() + { + Date = date + }; + + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + [RequiresRole(IdentityData.Roles.Admin)] + [HttpPost("largest-drive")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task>> GetUserWithLargestDrive( + [FromBody] DateTime date) + { + var query = new GetUserWithLargestDriveData.Query() + { + Date = date + }; + + 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 6a010c00..34ea8fd6 100644 --- a/src/Api/Controllers/DepartmentsController.cs +++ b/src/Api/Controllers/DepartmentsController.cs @@ -1,7 +1,12 @@ -using Application.Common.Models; -using Application.Departments.Commands.CreateDepartment; -using Application.Departments.Queries.GetAllDepartments; +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; @@ -10,32 +15,118 @@ namespace Api.Controllers; public class DepartmentsController : ApiControllerBase { + private readonly ICurrentUserService _currentUserService; + + public DepartmentsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Get a department by it 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) + { + var role = _currentUserService.GetRole(); + var userDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetDepartmentById.Query() + { + UserRole = role, + UserDepartmentId = userDepartmentId, + DepartmentId = departmentId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get rooms based on department 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 currentUserRole = _currentUserService.GetRole(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetRoomByDepartmentId.Query() + { + CurrentUserRole = currentUserRole, + CurrentUserDepartmentId = currentUserDepartmentId, + DepartmentId = departmentId, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + /// - /// Create a department + /// Get all departments /// - /// command parameter to create a department - /// Result[DepartmentDto] + /// A list of DepartmentDto + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task>>> GetAll() + { + var result = await Mediator.Send(new GetAllDepartments.Query()); + return Ok(Result>.Succeed(result)); + } + + /// + /// Add a department + /// + /// Add department details + /// A DepartmentDto of the the added department [RequiresRole(IdentityData.Roles.Admin)] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> CreateDepartment([FromBody] CreateDepartmentCommand command) + public async Task>> Add( + [FromBody] AddDepartmentRequest request) { + var command = new AddDepartment.Command() + { + Name = request.Name, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } /// - /// Get all documents + /// Delete a department /// - /// a Result of an IEnumerable of DepartmentDto - [HttpGet] + /// Id of the department to be deleted + /// A DepartmentDto of the deleted department + [RequiresRole(IdentityData.Roles.Admin)] + [HttpDelete("{departmentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task>>> GetAllDepartments() + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Delete([FromRoute] Guid departmentId) { - var result = await Mediator.Send(new GetAllDepartmentsQuery()); - return Ok(Result>.Succeed(result)); + var command = new DeleteDepartment.Command() + { + DepartmentId = departmentId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); } } \ No newline at end of file diff --git a/src/Api/Controllers/DocumentsController.cs b/src/Api/Controllers/DocumentsController.cs index 7d491b2b..b810cacc 100644 --- a/src/Api/Controllers/DocumentsController.cs +++ b/src/Api/Controllers/DocumentsController.cs @@ -1,12 +1,12 @@ +using Api.Controllers.Payload.Requests.Documents; +using Api.Controllers.Payload.Requests.Users; +using Application.Common.Extensions; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; -using Application.Departments.Commands.CreateDepartment; -using Application.Documents.Commands.ImportDocument; -using Application.Documents.Queries.GetAllDocumentsPaginated; -using Application.Documents.Queries.GetDocumentById; -using Application.Documents.Queries.GetDocumentTypes; +using Application.Documents.Commands; +using Application.Documents.Queries; using Application.Identity; -using Application.Users.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,60 +14,354 @@ namespace Api.Controllers; public class DocumentsController : ApiControllerBase { - [HttpPost] + 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.Employee)] + [HttpGet("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - public async Task>> ImportDocument([FromBody] ImportDocumentCommand command) + public async Task>> GetById( + [FromRoute] Guid documentId) { - var result = await Mediator.Send(command); + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetDocumentById.Query() + { + CurrentUser = currentUser, + DocumentId = documentId, + }; + 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.Admin, IdentityData.Roles.Staff)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + 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 + /// + /// A list of document types + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff, IdentityData.Roles.Employee)] [HttpGet("types")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task>>> GetAllDocumentTypes() { - var result = await Mediator.Send(new GetAllDocumentTypesQuery()); + var result = await Mediator.Send(new GetAllDocumentTypes.Query()); return Ok(Result>.Succeed(result)); } + + /// + /// Import a document + /// + /// Import document details + /// A DocumentDto of the imported document + [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) + { + 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, + FolderId = request.FolderId, + ImporterId = request.ImporterId, + IsPrivate = request.IsPrivate, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } - [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] - [HttpGet] + /// + /// Update a document + /// + /// Id of the document to be updated + /// Update document details + /// A DocumentDto of the updated document + [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>>> GetAllDocuments(Guid? roomId, Guid? lockerId, Guid? folderId, int? page, int? size, string? sortBy, string? sortOrder) + public async Task>> Update( + [FromRoute] Guid documentId, + [FromBody] UpdateDocumentRequest request) { - var query = new GetAllDocumentsPaginatedQuery() + var currentUser = _currentUserService.GetCurrentUser(); + if (currentUser.Department is null) + { + return Forbid(); + } + var query = new UpdateDocument.Command() { - RoomId = roomId, - LockerId = lockerId, - FolderId = folderId, - Page = page, - Size = size, - SortBy = sortBy, - SortOrder = sortOrder + CurrentUser = currentUser, + DocumentId = documentId, + Title = request.Title, + Description = request.Description, + DocumentType = request.DocumentType, + IsPrivate = request.IsPrivate, }; var result = await Mediator.Send(query); - return Ok(Result>.Succeed(result)); + return Ok(Result.Succeed(result)); + } + + /// + /// Delete a document + /// + /// Id of the document to be deleted + /// 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) + { + 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)); + } + - [HttpGet("{id:guid}")] - public async Task>> GetDocumentById(Guid id) + /// + /// Get shared users from a shared document paginated + /// + /// + /// + /// + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("{documentId:guid}/shared-users")] + public async Task>> GetSharedUsersByDocumentId( + [FromRoute] Guid documentId, + [FromQuery] GetAllSharedUsersPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetAllSharedUsersOfDocumentPaginated.Query() + { + CurrentUser = currentUser, + DocumentId = documentId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Download a file linked with a document + /// + /// Document id + /// File + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("{documentId:guid}/file")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DownloadFile( + [FromRoute] Guid documentId) { - var query = new GetDocumentByIdQuery() + var currentUser = _currentUserService.GetCurrentUser(); + var query = new DownloadDocumentFile.Query() { - Id = id + CurrentUser = currentUser, + DocumentId = documentId, }; var result = await Mediator.Send(query); + var content = new MemoryStream(result.FileData); + HttpContext.Response.ContentType = result.FileType; + return File(content, result.FileType, result.FileName); + } + + /// + /// Upload a file to link with a document + /// + /// Document id + /// + /// File + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost("{documentId:guid}/file")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> UploadFile( + [FromRoute] Guid documentId, + IFormFile file) + { + var currentUser = _currentUserService.GetCurrentUser(); + var fileData = new MemoryStream(); + await file.CopyToAsync(fileData); + var lastDotIndex = file.FileName.LastIndexOf(".", StringComparison.Ordinal); + var extension = file.FileName.Substring(lastDotIndex + 1, file.FileName.Length - lastDotIndex - 1); + + var command = new UploadFileToDocument.Command() + { + CurrentUser = currentUser, + DocumentId = documentId, + FileData = fileData, + FileType = file.ContentType, + FileExtension = extension, + }; + var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } -} \ No newline at end of file +} diff --git a/src/Api/Controllers/EntriesController.cs b/src/Api/Controllers/EntriesController.cs new file mode 100644 index 00000000..064e4c89 --- /dev/null +++ b/src/Api/Controllers/EntriesController.cs @@ -0,0 +1,215 @@ +using Api.Controllers.Payload.Requests.Entries; +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using Application.Entries.Commands; +using Application.Entries.Queries; +using Application.Identity; +using FluentValidation.Results; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; +using GetAllEntriesPaginatedQueryParameters = Api.Controllers.Payload.Requests.Entries.GetAllEntriesPaginatedQueryParameters; + +namespace Api.Controllers; + +public class EntriesController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + + public EntriesController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Upload a file or create a directory + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpPost] + public async Task>> UploadEntry( + [FromForm] UploadDigitalFileRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + + if (request is { IsDirectory: true, File: not null }) + { + throw new RequestValidationException(new List() + { + new("File", "Cannot create a directory with a file.") + }); + } + + if (request is { IsDirectory: false, File: null }) + { + throw new RequestValidationException(new List() + { + new("File", "Cannot upload with no files.") + }); + } + + CreateEntry.Command command; + + if (request.IsDirectory) + { + command = new CreateEntry.Command() + { + CurrentUser = currentUser, + Path = request.Path, + Name = request.Name, + IsDirectory = true, + FileType = null, + FileData = null, + FileExtension = null, + }; + } + else + { + var fileData = new MemoryStream(); + await request.File!.CopyToAsync(fileData); + var lastDotIndex = request.File.FileName.LastIndexOf(".", StringComparison.Ordinal); + var extension = + request.File.FileName.Substring(lastDotIndex + 1, request.File.FileName.Length - lastDotIndex - 1); + command = new CreateEntry.Command() + { + CurrentUser = currentUser, + Path = request.Path, + Name = request.Name, + IsDirectory = false, + FileType = request.File.ContentType, + FileData = fileData, + FileExtension = extension, + }; + } + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Update an entry + /// + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPut("{entryId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + + public async Task>> Update( + [FromRoute] Guid entryId, + [FromBody] UpdateEntryRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new UpdateEntry.Command() + { + Name = request.Name, + EntryId = entryId, + CurrentUser = currentUser + }; + + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + + /// + /// Get all entries paginated + /// + /// a paginated list of EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>>> GetAllPaginated( + [FromQuery] GetAllEntriesPaginatedQueryParameters queryParameters ) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetAllEntriesPaginated.Query() + { + CurrentUser = currentUser, + Page = queryParameters.Page, + Size = queryParameters.Size, + EntryPath = queryParameters.EntryPath, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder + }; + + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + + /// + /// Get an entry by Id + /// + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("{entryId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetById([FromRoute] Guid entryId) + { + var query = new GetEntryById.Query() + { + EntryId = entryId + }; + + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Share an entry + /// + /// + /// an EntryPermissionDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPut("{entryId:guid}/permissions")] + public async Task>> ManagePermission( + [FromRoute] Guid entryId, + [FromBody] ShareEntryPermissionRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new ShareEntry.Command + { + CurrentUser = currentUser, + EntryId = entryId, + UserId = request.UserId, + ExpiryDate = request.ExpiryDate, + CanView = request.CanView, + CanEdit = request.CanEdit, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Download a file + /// + /// + /// The download file + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("{entryId:guid}/file")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task DownloadFile([FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new DownloadDigitalFile.Command() + { + CurrentUser = currentUser, + EntryId = entryId + }; + + var result = await Mediator.Send(command); + HttpContext.Response.ContentType = result.FileType; + return File(result.Content, result.FileType, result.FileName); + } +} \ No newline at end of file diff --git a/src/Api/Controllers/FoldersController.cs b/src/Api/Controllers/FoldersController.cs index 77664c44..4ec4d2e5 100644 --- a/src/Api/Controllers/FoldersController.cs +++ b/src/Api/Controllers/FoldersController.cs @@ -1,7 +1,10 @@ +using Api.Controllers.Payload.Requests.Folders; +using Application.Common.Extensions; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; -using Application.Folders.Commands.AddFolder; -using Application.Folders.Commands.DisableFolder; +using Application.Folders.Commands; +using Application.Folders.Queries; using Application.Identity; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -10,22 +13,154 @@ namespace Api.Controllers; public class FoldersController : ApiControllerBase { - [RequiresRole(IdentityData.Roles.Staff)] + private readonly ICurrentUserService _currentUserService; + + public FoldersController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Get a folder by id + /// + /// Id of the folder to be retrieved + /// A FolderDto of the retrieved folder + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpGet("{folderId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [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); + return Ok(Result.Succeed(result)); + } + + /// + /// Get all folders paginated + /// + /// Get all folders paginated query parameters + /// A paginated list of FolderDto + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + 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, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Add a folder + /// + /// Add folder details + /// A FolderDto of the added folder + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [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, + LockerId = request.LockerId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Remove a folder + /// + /// Id of the folder to be removed + /// A FolderDto of the removed folder + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpDelete("{folderId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> AddFolder([FromBody] AddFolderCommand command) + public async Task>> RemoveFolder([FromRoute] Guid folderId) { + var currentUser = _currentUserService.GetCurrentUser(); + Guid? staffRoomId = null; + if (currentUser.Role.IsStaff()) + { + 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)); } - [HttpPut("disable")] - public async Task>> DisableFolder([FromBody] DisableFolderCommand command) + /// + /// Update a folder + /// + /// Id of the folder to be updated + /// Update folder details + /// A FolderDto of the updated folder + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpPut("{folderId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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, + Capacity = request.Capacity, + IsAvailable = request.IsAvailable, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } diff --git a/src/Api/Controllers/ImportRequestsController.cs b/src/Api/Controllers/ImportRequestsController.cs new file mode 100644 index 00000000..d2875e22 --- /dev/null +++ b/src/Api/Controllers/ImportRequestsController.cs @@ -0,0 +1,188 @@ +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, + Statuses = queryParameters.Statuses, + 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 1dd1b3ce..58d1c502 100644 --- a/src/Api/Controllers/LockersController.cs +++ b/src/Api/Controllers/LockersController.cs @@ -1,9 +1,10 @@ -using Application.Common.Models; +using Api.Controllers.Payload.Requests.Lockers; +using Application.Common.Interfaces; +using Application.Common.Models; using Application.Common.Models.Dtos.Physical; using Application.Identity; -using Application.Lockers.Commands.AddLocker; -using Application.Lockers.Commands.DisableLocker; -using Application.Lockers.Commands.EnableLocker; +using Application.Lockers.Commands; +using Application.Lockers.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -11,28 +12,146 @@ namespace Api.Controllers; public class LockersController : ApiControllerBase { - [RequiresRole(IdentityData.Roles.Staff)] + 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) + { + 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); + return Ok(Result.Succeed(result)); + } + + /// + /// Get all lockers paginated + /// + /// 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 currentUserId = _currentUserService.GetId(); + var currentUserRole = _currentUserService.GetRole(); + var currentUserDepartmentId = _currentUserService.GetDepartmentId(); + var query = new GetAllLockersPaginated.Query() + { + CurrentUserId = currentUserId, + CurrentUserRole = currentUserRole, + CurrentUserDepartmentId = currentUserDepartmentId, + RoomId = queryParameters.RoomId, + 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)); + } + + /// + /// Add a locker + /// + /// Add locker details + /// A LockerDto of the added locker + [RequiresRole(IdentityData.Roles.Admin)] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> AddLocker([FromBody] AddLockerCommand command) + 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, + RoomId = request.RoomId, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } - [HttpPut("disable")] - public async Task>> DisableLocker([FromBody] DisableLockerCommand command) + /// + /// Remove a locker + /// + /// 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)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [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); return Ok(Result.Succeed(result)); } - [HttpPut("enable")] - public async Task>> EnableLocker([FromBody] EnableLockerCommand command) + /// + /// Update a 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("{lockerId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Update( + [FromRoute] Guid lockerId, + [FromBody] UpdateLockerRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new UpdateLocker.Command() + { + CurrentUser = currentUser, + LockerId = lockerId, + Name = request.Name, + Description = request.Description, + Capacity = request.Capacity, + IsAvailable = request.IsAvailable, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } diff --git a/src/Api/Controllers/LogsController.cs b/src/Api/Controllers/LogsController.cs new file mode 100644 index 00000000..92d7560e --- /dev/null +++ b/src/Api/Controllers/LogsController.cs @@ -0,0 +1,34 @@ +using Api.Controllers.Payload.Requests; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Identity; +using Application.Logs.Queries; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +public class LogsController : ApiControllerBase +{ + /// + /// Get logs paginated + /// + /// + /// + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet] + public async Task>> GetAllPaginated( + [FromQuery] GetAllLogsPaginatedQueryParameters queryParameters) + { + var query = new GetAllLogsPaginated.Query() + { + ObjectId = queryParameters.ObjectId, + ObjectType = queryParameters.ObjectType, + 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/Payload/Requests/Auth/LoginModel.cs b/src/Api/Controllers/Payload/Requests/Auth/LoginModel.cs new file mode 100644 index 00000000..99f49b03 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Auth/LoginModel.cs @@ -0,0 +1,16 @@ +namespace Api.Controllers.Payload.Requests.Auth; + +/// +/// Login credentials to login +/// +public class LoginModel +{ + /// + /// Email of user + /// + public string Email { get; set; } = null!; + /// + /// Password of user + /// + public string Password { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Auth/RefreshTokenRequest.cs b/src/Api/Controllers/Payload/Requests/Auth/RefreshTokenRequest.cs new file mode 100644 index 00000000..979dc447 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Auth/RefreshTokenRequest.cs @@ -0,0 +1,16 @@ +namespace Api.Controllers.Payload.Requests.Auth; + +/// +/// Request details to refresh token +/// +public class RefreshTokenRequest +{ + /// + /// Access token + /// + public string Token { get; set; } = null!; + /// + /// Refresh token + /// + public string RefreshToken { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Auth/ResetPasswordRequest.cs b/src/Api/Controllers/Payload/Requests/Auth/ResetPasswordRequest.cs new file mode 100644 index 00000000..2e18993f --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Auth/ResetPasswordRequest.cs @@ -0,0 +1,8 @@ +namespace Api.Controllers.Payload.Requests.Auth; + +public class ResetPasswordRequest +{ + public string Token { get; set; } + public string NewPassword { get; set; } + public string ConfirmPassword { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/BinEntries/GetAllBinEntriesPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/BinEntries/GetAllBinEntriesPaginatedQueryParameters.cs new file mode 100644 index 00000000..19f038fd --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/BinEntries/GetAllBinEntriesPaginatedQueryParameters.cs @@ -0,0 +1,7 @@ +namespace Api.Controllers.Payload.Requests.BinEntries; + +public class GetAllBinEntriesPaginatedQueryParameters : PaginatedQueryParameters +{ + public string EntryPath { get; set; } = null!; + public string? SearchTerm { get; set; } +} \ No newline at end of file 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/BorrowDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/BorrowDocumentRequest.cs new file mode 100644 index 00000000..f3a0085d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/BorrowDocumentRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Request details to borrow a document +/// +public class BorrowDocumentRequest +{ + /// + /// Id of the document to be borrowed + /// + public Guid DocumentId { get; set; } + /// + /// Borrow from + /// + public DateTime BorrowFrom { get; set; } + /// + /// Borrow to + /// + public DateTime BorrowTo { get; set; } + /// + /// Reason of borrowing + /// + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs new file mode 100644 index 00000000..922651ed --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as employee +/// +public class GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters : PaginatedQueryParameters +{ + public Guid? DocumentId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs new file mode 100644 index 00000000..d9656766 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs @@ -0,0 +1,10 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as staff +/// +public class GetAllBorrowRequestsPaginatedAsStaffQueryParameters : PaginatedQueryParameters +{ + 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/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs new file mode 100644 index 00000000..ca2c22b7 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class GetAllBorrowRequestsPaginatedForDocumentQueryParameters : PaginatedQueryParameters +{ + public string? Status { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs new file mode 100644 index 00000000..7fc42536 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedQueryParameters.cs @@ -0,0 +1,15 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as admin +/// +public class GetAllBorrowRequestsPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the room to get borrow requests in + /// + public Guid? RoomId { get; set; } + public Guid? DocumentId { get; set; } + public Guid? EmployeeId { get; set; } + public string[]? Statuses { get; init; } +} \ 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/Borrows/UpdateBorrowRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/UpdateBorrowRequest.cs new file mode 100644 index 00000000..dbbef730 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/UpdateBorrowRequest.cs @@ -0,0 +1,8 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class UpdateBorrowRequest +{ + public DateTime BorrowFrom { get; init; } + public DateTime BorrowTo { get; init; } + public string Reason { get; init; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Dashboard/GetImportedDocumentsMetricsRequest.cs b/src/Api/Controllers/Payload/Requests/Dashboard/GetImportedDocumentsMetricsRequest.cs new file mode 100644 index 00000000..95283540 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Dashboard/GetImportedDocumentsMetricsRequest.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Dashboard; + +public class GetImportedDocumentsMetricsRequest +{ + + public DateTime StartDate { get; set; } + + public DateTime EndDate { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Departments/AddDepartmentRequest.cs b/src/Api/Controllers/Payload/Requests/Departments/AddDepartmentRequest.cs new file mode 100644 index 00000000..2f6444f7 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Departments/AddDepartmentRequest.cs @@ -0,0 +1,12 @@ +namespace Api.Controllers.Payload.Requests.Departments; + +/// +/// Request details to add a department +/// +public class AddDepartmentRequest +{ + /// + /// Name of the department to be added + /// + public string Name { get; init; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Departments/UpdateDepartmentRequest.cs b/src/Api/Controllers/Payload/Requests/Departments/UpdateDepartmentRequest.cs new file mode 100644 index 00000000..48ad1052 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Departments/UpdateDepartmentRequest.cs @@ -0,0 +1,12 @@ +namespace Api.Controllers.Payload.Requests.Departments; + +/// +/// Request details to update a department +/// +public class UpdateDepartmentRequest +{ + /// + /// New name of the department to be updated + /// + public string Name { get; set; } = null!; +} \ 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..88b9ff9e --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForEmployeePaginatedQueryParameters.cs @@ -0,0 +1,8 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllDocumentsForEmployeePaginatedQueryParameters : PaginatedQueryParameters +{ + 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 new file mode 100644 index 00000000..94cca9b8 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs @@ -0,0 +1,28 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +/// +/// Query parameters for getting all documents with pagination +/// +public class GetAllDocumentsPaginatedQueryParameters : PaginatedQueryParameters +{ + public Guid? UserId { get; set; } + /// + /// Id of the room to find documents in + /// + public Guid? RoomId { get; set; } + /// + /// 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; } + 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/ImportDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/ImportDocumentRequest.cs new file mode 100644 index 00000000..49cea0fe --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/ImportDocumentRequest.cs @@ -0,0 +1,32 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +/// +/// Request details to import a document +/// +public class ImportDocumentRequest +{ + /// + /// 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!; + /// + /// Id of the importer + /// + public Guid ImporterId { get; set; } + /// + /// Id of the folder that this document will be in + /// + public Guid FolderId { get; set; } + /// + /// + /// + public bool IsPrivate { 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 new file mode 100644 index 00000000..97626d8a --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/UpdateDocumentRequest.cs @@ -0,0 +1,21 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +/// +/// Request details to update a document +/// +public class UpdateDocumentRequest +{ + /// + /// New title of the document to be updated + /// + public string Title { get; set; } = null!; + /// + /// New description of the document to be updated + /// + public string? Description { get; set; } + /// + /// 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/Entries/GetAllEntriesPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Entries/GetAllEntriesPaginatedQueryParameters.cs new file mode 100644 index 00000000..462cbbc0 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/GetAllEntriesPaginatedQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +/// +/// Get All Entries Paginated Query Parameters +/// +public class GetAllEntriesPaginatedQueryParameters : PaginatedQueryParameters +{ + public string EntryPath { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Entries/GetAllSharedEntriesPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Entries/GetAllSharedEntriesPaginatedQueryParameters.cs new file mode 100644 index 00000000..309f67c6 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/GetAllSharedEntriesPaginatedQueryParameters.cs @@ -0,0 +1,12 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +/// +/// Get All Shared Entries Paginated Query Parameters +/// +public class GetAllSharedEntriesPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Entry id + /// + public Guid? EntryId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Entries/ShareEntryPermissionRequest.cs b/src/Api/Controllers/Payload/Requests/Entries/ShareEntryPermissionRequest.cs new file mode 100644 index 00000000..98b64b2f --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/ShareEntryPermissionRequest.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +public class ShareEntryPermissionRequest +{ + public Guid UserId { get; set; } + public DateTime? ExpiryDate { get; set; } + public bool CanView { get; set; } + public bool CanEdit { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Entries/UpdateEntryRequest.cs b/src/Api/Controllers/Payload/Requests/Entries/UpdateEntryRequest.cs new file mode 100644 index 00000000..d1fb81f8 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/UpdateEntryRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +public class UpdateEntryRequest +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Entries/UploadDigitalFileRequest.cs b/src/Api/Controllers/Payload/Requests/Entries/UploadDigitalFileRequest.cs new file mode 100644 index 00000000..ff4ddbf9 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/UploadDigitalFileRequest.cs @@ -0,0 +1,12 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +/// +/// +/// +public class UploadDigitalFileRequest +{ + public string Name { get; set; } = null!; + public string Path { get; set; } = null!; + public bool IsDirectory { get; set; } + public IFormFile? File { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Entries/UploadSharedEntryRequest.cs b/src/Api/Controllers/Payload/Requests/Entries/UploadSharedEntryRequest.cs new file mode 100644 index 00000000..21363822 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Entries/UploadSharedEntryRequest.cs @@ -0,0 +1,11 @@ +namespace Api.Controllers.Payload.Requests.Entries; + +/// +/// +/// +public class UploadSharedEntryRequest +{ + public string Name { get; set; } = null!; + public bool IsDirectory { get; set; } + public IFormFile? File { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Folders/AddFolderRequest.cs b/src/Api/Controllers/Payload/Requests/Folders/AddFolderRequest.cs new file mode 100644 index 00000000..1dd731c0 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Folders/AddFolderRequest.cs @@ -0,0 +1,23 @@ +namespace Api.Controllers.Payload.Requests.Folders; +/// +/// Request details to add a folder +/// +public class AddFolderRequest +{ + /// + /// Name of the folder to be added + /// + public string Name { get; init; } = null!; + /// + /// Description of the folder to be added + /// + public string? Description { get; init; } + /// + /// Number of documents this folder can hold + /// + public int Capacity { get; init; } + /// + /// Id of the locker that this folder will be in + /// + public Guid LockerId { get; init; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs new file mode 100644 index 00000000..298a30b0 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs @@ -0,0 +1,20 @@ +namespace Api.Controllers.Payload.Requests.Folders; + +/// +/// Query parameters for getting all folders with pagination +/// +public class GetAllFoldersPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } + /// + /// Id of the room to find folders in + /// + public Guid? RoomId { get; set; } + /// + /// Id of the locker to find folders in + /// + public Guid? LockerId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Folders/UpdateFolderRequest.cs b/src/Api/Controllers/Payload/Requests/Folders/UpdateFolderRequest.cs new file mode 100644 index 00000000..b62943b2 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Folders/UpdateFolderRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Folders; + +/// +/// Request details to update a folder +/// +public class UpdateFolderRequest +{ + /// + /// New name of the folder to be updated + /// + public string Name { get; set; } = null!; + /// + /// New description of the folder to be updated + /// + public string? Description { get; set; } + /// + /// New capacity of the folder to be updated + /// + public int Capacity { get; set; } + /// + /// Status of folder to be updated + /// + public bool IsAvailable { 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..68917040 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/GetAllLogsPaginatedQueryParameters.cs @@ -0,0 +1,28 @@ +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; } + /// + /// Object Id + /// + public Guid? ObjectId { get; set; } + /// + /// Object type + /// + public string ObjectType { get; set; } = null!; +} \ 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..dfb22205 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/ImportRequests/GetAllImportRequestsPaginatedQueryParameters.cs @@ -0,0 +1,11 @@ +namespace Api.Controllers.Payload.Requests.ImportRequests; + +/// +/// +/// +public class GetAllImportRequestsPaginatedQueryParameters : PaginatedQueryParameters +{ + public string? SearchTerm { get; set; } + public Guid? RoomId { get; set; } + public string[]? Statuses { 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 new file mode 100644 index 00000000..21374f9a --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Lockers/AddLockerRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Lockers; + +/// +/// Request details to add a locker +/// +public class AddLockerRequest +{ + /// + /// Name of the locker to be updated + /// + public string Name { get; set; } = null!; + /// + /// Description of the locker to be updated + /// + public string? Description { get; set; } + /// + /// Id of the room that this locker will be in + /// + public Guid RoomId { get; set; } + /// + /// Number of folders this locker can hold + /// + public int Capacity { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs new file mode 100644 index 00000000..25372261 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.Payload.Requests.Lockers; + +/// +/// Query parameters for getting all lockers with pagination +/// +public class GetAllLockersPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the room to find lockers in + /// + public Guid? RoomId { get; set; } + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Lockers/UpdateLockerRequest.cs b/src/Api/Controllers/Payload/Requests/Lockers/UpdateLockerRequest.cs new file mode 100644 index 00000000..a6daa0aa --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Lockers/UpdateLockerRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Lockers; + +/// +/// Request details to update a locker +/// +public class UpdateLockerRequest +{ + /// + /// New name of the locker to be updated + /// + public string Name { get; set; } = null!; + /// + /// New description of the locker to be updated + /// + public string? Description { get; set; } + /// + /// New capacity of the locker to be updated + /// + public int Capacity { get; set; } + /// + /// Status of locker to be updated + /// + public bool IsAvailable { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/LoginModel.cs b/src/Api/Controllers/Payload/Requests/LoginModel.cs deleted file mode 100644 index d5bd6c61..00000000 --- a/src/Api/Controllers/Payload/Requests/LoginModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Api.Controllers.Payload.Requests; - -public class LoginModel -{ - public string Email { get; set; } - public string Password { get; set; } -} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs new file mode 100644 index 00000000..d29c3838 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs @@ -0,0 +1,21 @@ +namespace Api.Controllers.Payload.Requests; + +public class PaginatedQueryParameters +{ + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Size number + /// + public int? Size { get; set; } + /// + /// Sort criteria + /// + public string? SortBy { get; set; } + /// + /// Sort direction + /// + public string? SortOrder { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/RefreshTokenRequest.cs b/src/Api/Controllers/Payload/Requests/RefreshTokenRequest.cs deleted file mode 100644 index bcc3d8e2..00000000 --- a/src/Api/Controllers/Payload/Requests/RefreshTokenRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Api.Controllers.Payload.Requests; - -public class RefreshTokenRequest -{ - public string Token { get; set; } - public string RefreshToken { get; set; } -} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Rooms/AddRoomRequest.cs b/src/Api/Controllers/Payload/Requests/Rooms/AddRoomRequest.cs new file mode 100644 index 00000000..4228f4da --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Rooms/AddRoomRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Rooms; + +/// +/// Request details to add a room +/// +public class AddRoomRequest +{ + /// + /// Name of the room to be added + /// + public string Name { get; set; } = null!; + /// + /// Description of the room to be added + /// + public string? Description { get; set; } + /// + /// Number of lockers this room can hold + /// + public int Capacity { get; set; } + /// + /// Id of the department this room belongs to + /// + public Guid DepartmentId { 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 new file mode 100644 index 00000000..83ff67a1 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs @@ -0,0 +1,14 @@ +namespace Api.Controllers.Payload.Requests.Rooms; + +/// +/// Query parameters for getting all rooms with pagination +/// +public class GetAllRoomsPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } + public bool? IsAvailable { get; set; } + public Guid? DepartmentId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Rooms/GetEmptyContainersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Rooms/GetEmptyContainersPaginatedQueryParameters.cs new file mode 100644 index 00000000..2d60bb6d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Rooms/GetEmptyContainersPaginatedQueryParameters.cs @@ -0,0 +1,16 @@ +namespace Api.Controllers.Payload.Requests.Rooms; + +/// +/// Query parameters for getting all empty containers in a room +/// +public class GetEmptyContainersPaginatedQueryParameters +{ + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Size number + /// + public int? Size { 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 new file mode 100644 index 00000000..d277d097 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Rooms/UpdateRoomRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Rooms; + +/// +/// Request details to update a room +/// +public class UpdateRoomRequest +{ + /// + /// New name of the room to be updated + /// + public string Name { get; set; } = null!; + /// + /// New description of the room to be updated + /// + public string? Description { get; set; } + /// + /// 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 new file mode 100644 index 00000000..6e33caa6 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Staffs/AddStaffRequest.cs @@ -0,0 +1,16 @@ +namespace Api.Controllers.Payload.Requests.Staffs; + +/// +/// Request details to add a staff +/// +public class AddStaffRequest +{ + /// + /// User id of the new staff + /// + public Guid StaffId { get; init; } + /// + /// Id of the room this staff will be in + /// + public Guid? RoomId { get; init; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs new file mode 100644 index 00000000..323ec479 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs @@ -0,0 +1,12 @@ +namespace Api.Controllers.Payload.Requests.Staffs; + +/// +/// Query parameters for getting all staffs with pagination +/// +public class GetAllStaffsPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Users/AddUserRequest.cs b/src/Api/Controllers/Payload/Requests/Users/AddUserRequest.cs new file mode 100644 index 00000000..ad129e16 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/AddUserRequest.cs @@ -0,0 +1,36 @@ +namespace Api.Controllers.Payload.Requests.Users; + +/// +/// Request details to add a user +/// +public class AddUserRequest +{ + /// + /// Username of the user to be added + /// + public string Username { get; init; } = null!; + /// + /// Email of the user to be added + /// + public string Email { get; init; } = null!; + /// + /// First name of the user to be added + /// + public string? FirstName { get; init; } + /// + /// Last name of the user to be added + /// + public string? LastName { get; init; } + /// + /// Department of the user to be added + /// + public Guid DepartmentId { get; init; } + /// + /// Role of the user to be added + /// + public string Role { get; init; } = null!; + /// + /// Position of the user to be added + /// + public string? Position { get; init; } +} \ No newline at end of file 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/GetAllSharedUsersOfASharedEntryPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Users/GetAllSharedUsersOfASharedEntryPaginatedQueryParameters.cs new file mode 100644 index 00000000..bdb7a102 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/GetAllSharedUsersOfASharedEntryPaginatedQueryParameters.cs @@ -0,0 +1,17 @@ +namespace Api.Controllers.Payload.Requests.Users; + +public class GetAllSharedUsersPaginatedQueryParameters +{ + /// + /// Search term + /// + public string? SearchTerm { get; set; } + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Size number + /// + public int? Size { 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 new file mode 100644 index 00000000..738a1017 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs @@ -0,0 +1,17 @@ +namespace Api.Controllers.Payload.Requests.Users; + +/// +/// Query parameters for getting all users with pagination +/// +public class GetAllUsersPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the department to find users in + /// + public Guid[]? DepartmentIds { get; set; } + public string? Role { get; set; } + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file 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 new file mode 100644 index 00000000..959567d4 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Users/UpdateUserRequest.cs @@ -0,0 +1,22 @@ +namespace Api.Controllers.Payload.Requests.Users; + +/// +/// Request details to update a user +/// +public class UpdateUserRequest +{ + /// + /// 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; } + /// + /// 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/Payload/Responses/LoginResult.cs b/src/Api/Controllers/Payload/Responses/LoginResult.cs index 29fb21c4..f731ac4a 100644 --- a/src/Api/Controllers/Payload/Responses/LoginResult.cs +++ b/src/Api/Controllers/Payload/Responses/LoginResult.cs @@ -1,3 +1,4 @@ +using Application.Common.Models.Dtos; using Application.Users.Queries; namespace Api.Controllers.Payload.Responses; diff --git a/src/Api/Controllers/Payload/Responses/NotActivatedLoginResut.cs b/src/Api/Controllers/Payload/Responses/NotActivatedLoginResut.cs new file mode 100644 index 00000000..d57edbfe --- /dev/null +++ b/src/Api/Controllers/Payload/Responses/NotActivatedLoginResut.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Responses; + +public class NotActivatedLoginResult +{ + public string Token { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/RoomsController.cs b/src/Api/Controllers/RoomsController.cs index 8d8916d6..a285a3fd 100644 --- a/src/Api/Controllers/RoomsController.cs +++ b/src/Api/Controllers/RoomsController.cs @@ -1,10 +1,11 @@ +using Api.Controllers.Payload.Requests.Rooms; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; using Application.Identity; -using Application.Rooms.Commands.CreateRoom; -using Application.Rooms.Commands.DisableRoom; -using Application.Rooms.Commands.RemoveRoom; -using Application.Rooms.Queries.GetEmptyContainersPaginated; +using Application.Rooms.Commands; +using Application.Rooms.Queries; +using Application.Staffs.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,44 +13,192 @@ namespace Api.Controllers; public class RoomsController : ApiControllerBase { - [RequiresRole(IdentityData.Roles.Admin)] - [HttpPost] + 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.Status400BadRequest)] - public async Task>> AddRoom(CreateRoomCommand command) + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetById( + [FromRoute] Guid roomId) { - var result = await Mediator.Send(command); + 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); return Ok(Result.Succeed(result)); } + + /// + /// Get all rooms paginated + /// + /// Get all rooms paginated details + /// A paginated list of rooms + [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, + IsAvailable = queryParameters.IsAvailable, + 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 empty containers in a room + /// + /// + /// Get empty containers paginated details + /// A paginated list of EmptyLockerDto [RequiresRole(IdentityData.Roles.Staff)] - [HttpPost("empty-containers")] + [HttpPost("{roomId:guid}/empty-containers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetEmptyContainers(GetEmptyContainersPaginatedQuery query) + public async Task>> GetEmptyContainers( + [FromRoute] Guid roomId, + [FromQuery] GetEmptyContainersPaginatedQueryParameters queryParameters) { + var query = new GetEmptyContainersPaginated.Query() + { + RoomId = roomId, + Page = queryParameters.Page, + Size = queryParameters.Size, + }; var result = await Mediator.Send(query); return Ok(Result>.Succeed(result)); } - - [HttpPut] + + /// + /// 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 + /// + /// Add room details + /// A RoomDto of the added room + [RequiresRole(IdentityData.Roles.Admin)] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] + 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, + DepartmentId = request.DepartmentId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Remove a room + /// + /// Id of the room to be removed + /// A RoomDto of the removed room + [RequiresRole(IdentityData.Roles.Admin)] + [HttpDelete("{roomId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> DisableRoom(DisableRoomCommand command) + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> RemoveRoom( + [FromRoute] Guid roomId) { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new RemoveRoom.Command() + { + CurrentUser = currentUser, + RoomId = roomId, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } - [HttpDelete] + /// + /// Update a room + /// + /// 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.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> RemoveRoom(RemoveRoomCommand command) + [ProducesResponseType(StatusCodes.Status409Conflict)] + 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)); } diff --git a/src/Api/Controllers/SharedController.cs b/src/Api/Controllers/SharedController.cs new file mode 100644 index 00000000..165a9979 --- /dev/null +++ b/src/Api/Controllers/SharedController.cs @@ -0,0 +1,201 @@ +using Api.Controllers.Payload.Requests.Entries; +using Application.Common.Exceptions; +using Api.Controllers.Payload.Requests.Users; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using Application.Entries.Commands; +using Application.Entries.Queries; +using Application.Identity; +using FluentValidation.Results; +using Application.Users.Queries; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +public class SharedController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + + public SharedController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + + /// + /// Download a shared file + /// + /// + /// the download file + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries/{entryId:guid}/file")] + public async Task DownloadSharedFile([FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new DownloadSharedEntry.Query() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + var result = await Mediator.Send(query); + var content = new MemoryStream(result.FileData); + HttpContext.Response.ContentType = result.FileType; + return File(content, result.FileType, result.FileName); + } + + /// + /// Get all shared entries paginated + /// + /// a paginated list of EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries")] + public async Task>> GetAll( + [FromQuery] GetAllSharedEntriesPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetAllSharedEntriesPaginated.Query() + { + CurrentUser = currentUser, + Page = queryParameters.Page, + Size = queryParameters.Size, + EntryId = queryParameters.EntryId, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + + /// + /// Upload a file or create a directory to a shared entry + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost("entries/{entryId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> CreateSharedEntry( + [FromRoute] Guid entryId, + [FromForm] UploadSharedEntryRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + if (request is { IsDirectory: true, File: not null }) + { + throw new RequestValidationException(new List() + { + new("File", "Cannot create a directory with a file.") + }); + } + + if (request is { IsDirectory: false, File: null }) + { + throw new RequestValidationException(new List() + { + new("File", "Cannot upload with no files.") + }); + } + + CreateSharedEntry.Command command; + + if (request.IsDirectory) + { + command = new CreateSharedEntry.Command() + { + Name = request.Name, + CurrentUser = currentUser, + EntryId = entryId, + FileData = null, + FileExtension = null, + FileType = null, + IsDirectory = true + }; + } + else + { + var fileData = new MemoryStream(); + await request.File!.CopyToAsync(fileData); + var lastDotIndex = request.File.FileName.LastIndexOf(".", StringComparison.Ordinal); + var extension = + request.File.FileName.Substring(lastDotIndex + 1, request.File.FileName.Length - lastDotIndex - 1); + command = new CreateSharedEntry.Command() + { + CurrentUser = currentUser, + EntryId = entryId, + Name = request.Name, + IsDirectory = false, + FileType = request.File.ContentType, + FileData = fileData, + FileExtension = extension, + }; + } + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Get shared users from a shared entries paginated + /// + /// a paginated list of UserDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries/{entryId:guid}/shared-users")] + public async Task>> GetSharedUsersFromASharedEntryPaginated( + [FromRoute] Guid entryId, + [FromQuery] GetAllSharedUsersPaginatedQueryParameters queryParameters) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetAllSharedUsersOfASharedEntryPaginated.Query() + { + CurrentUser = currentUser, + Page = queryParameters.Page, + Size = queryParameters.Size, + SearchTerm = queryParameters.SearchTerm, + EntryId = entryId, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + + /// + /// Share an entry to a user + /// + /// + /// an EntryPermissionDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries/{entryId}/permissions")] + public async Task>> SharePermissions( + [FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetPermissions.Query + { + CurrentUser = currentUser, + EntryId = entryId + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get shared entry by Id + /// + /// an EntryDto + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("entries/{entryId:guid}")] + public async Task>> GetSharedEntryById( + [FromRoute] Guid entryId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetSharedEntryById.Query() + { + CurrentUser = currentUser, + EntryId = entryId, + }; + 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 255335ef..19c196b7 100644 --- a/src/Api/Controllers/StaffsController.cs +++ b/src/Api/Controllers/StaffsController.cs @@ -1,7 +1,11 @@ +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.Staffs.Commands.CreateStaff; -using Application.Users.Queries.Physical; +using Application.Rooms.Queries; +using Application.Staffs.Commands; +using Application.Staffs.Queries; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -9,13 +13,125 @@ 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) + { + var query = new GetStaffById.Query() + { + StaffId = staffId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get room by staff id + /// + /// 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>> GetRoomByStaffId( + [FromRoute] Guid staffId) + { + var currentUser = _currentUserService.GetCurrentUser(); + var query = new GetRoomByStaffId.Query() + { + CurrentUser = currentUser, + StaffId = staffId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get all staffs paginated + /// + /// Get all staffs query parameters + /// A paginated list of StaffDto + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task>>> GetAllPaginated( + [FromQuery] GetAllStaffsPaginatedQueryParameters queryParameters) + { + var query = new GetAllStaffsPaginated.Query() + { + 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)); + } + + /// + /// Assign a staff + /// + /// Add staff details + /// A StaffDto of the added staff [RequiresRole(IdentityData.Roles.Admin)] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> CreateStaff([FromBody] CreateStaffCommand command) + public async Task>> Assign( + [FromBody] AddStaffRequest request) + { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new AssignStaff.Command() + { + CurrentUser = currentUser, + RoomId = request.RoomId, + StaffId = request.StaffId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Remove a staff from room + /// + /// 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>> 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)); } diff --git a/src/Api/Controllers/UsersController.cs b/src/Api/Controllers/UsersController.cs index 33aa1714..aed6dfa8 100644 --- a/src/Api/Controllers/UsersController.cs +++ b/src/Api/Controllers/UsersController.cs @@ -1,54 +1,188 @@ +using Api.Controllers.Payload.Requests.Users; +using Application.Common.Interfaces; using Application.Common.Models; using Application.Identity; -using Application.Users.Commands.CreateUser; -using Application.Users.Commands.DisableUser; +using Application.Users.Commands; using Application.Users.Queries; -using Application.Users.Queries.GetUsersByName; using Infrastructure.Identity.Authorization; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers; public class UsersController : ApiControllerBase { - [HttpPost] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + 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)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> CreateUser([FromBody] CreateUserCommand command) + public async Task>> GetById([FromRoute] Guid userId) { - var result = await Mediator.Send(command); + 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); return Ok(Result.Succeed(result)); } - - [Authorize] + + /// + /// Get all users paginated + /// + /// Get all users query parameters + /// A paginated list of UserDto [RequiresRole(IdentityData.Roles.Admin)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task>>> GetUsersByName(string? searchTerm, int? page, int? size) + public async Task>>> GetAllPaginated( + [FromQuery] GetAllUsersPaginatedQueryParameters queryParameters) { - var query = new GetUsersByNameQuery + var query = new GetAllUsersPaginated.Query() { - SearchTerm = searchTerm, - Page = page, - Size = size + 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)); } - - [HttpPost("disable")] + + /// + /// 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, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Add a user + /// + /// Add user details + /// A UserDto of the added user + [RequiresRole(IdentityData.Roles.Admin)] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [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, + LastName = request.LastName, + Role = request.Role, + Position = request.Position, + DepartmentId = request.DepartmentId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Update a user + /// + /// Id of the user to be updated + /// Update user details + /// A UserDto of the updated user [RequiresRole(IdentityData.Roles.Admin)] + [HttpPut("{userId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Update( + [FromRoute] Guid userId, + [FromBody] UpdateUserRequest request) + { + 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)); + } + + /// + /// Update a user + /// + /// Update user details + /// A UserDto of the updated user + [RequiresRole(IdentityData.Roles.Staff, IdentityData.Roles.Employee)] + [HttpPut("self")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> DisableUser([FromBody] DisableUserCommand command) + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> UpdateSelf( + [FromBody] UpdateSelfRequest request) { + var currentUser = _currentUserService.GetCurrentUser(); + var command = new UpdateUser.Command() + { + CurrentUser = currentUser, + UserId = currentUser.Id, + FirstName = request.FirstName, + LastName = request.LastName, + Position = currentUser.Position, + Role = currentUser.Role, + IsActive = currentUser.IsActive, + }; var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } -} +} \ No newline at end of file diff --git a/src/Api/Extensions/HostExtensions.cs b/src/Api/Extensions/HostExtensions.cs index db1d0189..c736627e 100644 --- a/src/Api/Extensions/HostExtensions.cs +++ b/src/Api/Extensions/HostExtensions.cs @@ -23,7 +23,6 @@ public static IHost MigrateDatabase(this IHost host, Action((context, _) => + { + ApplicationDbContextSeed.Seed(context, configuration, Log.Logger).Wait(); + }); } - else + + if (app.Environment.IsEnvironment("Testing")) + { + app.MigrateDatabase((_, _) => + { + }); + } + + if (app.Environment.IsEnvironment("Production")) { + app.UseCors(CORSPolicy.Production); + app.MigrateDatabase((_, _) => + { + // TODO: should only generate admin account + }); } app.UseAuthentication(); diff --git a/src/Api/Middlewares/ExceptionMiddleware.cs b/src/Api/Middlewares/ExceptionMiddleware.cs index 194d73ab..a99cbdf5 100644 --- a/src/Api/Middlewares/ExceptionMiddleware.cs +++ b/src/Api/Middlewares/ExceptionMiddleware.cs @@ -35,6 +35,7 @@ public ExceptionMiddleware() { typeof(InvalidOperationException), HandleInvalidOperationException }, { typeof(AuthenticationException), HandleAuthenticationException }, { typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException }, + { typeof(NotChangedException), HandleNotChangedException }, }; } @@ -54,25 +55,25 @@ private async Task HandleExceptionAsync(HttpContext context, Exception ex) } - private async void HandleKeyNotFoundException(HttpContext context, Exception ex) + private static async void HandleKeyNotFoundException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status404NotFound; await WriteExceptionMessageAsync(context, ex); } - private async void HandleConflictException(HttpContext context, Exception ex) + private static async void HandleConflictException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; await WriteExceptionMessageAsync(context, ex); } - private async void HandleNotAllowedException(HttpContext context, Exception ex) + private static async void HandleNotAllowedException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status406NotAcceptable; await WriteExceptionMessageAsync(context, ex); } - private async void HandleRequestValidationException(HttpContext context, Exception ex) + private static async void HandleRequestValidationException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status400BadRequest; @@ -86,29 +87,34 @@ private async void HandleRequestValidationException(HttpContext context, Excepti await context.Response.Body.WriteAsync(SerializeToUtf8BytesWeb(result)); } - private async void HandleLimitExceededException(HttpContext context, Exception ex) + private static async void HandleLimitExceededException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; await WriteExceptionMessageAsync(context, ex); } - private async void HandleAuthenticationException(HttpContext context, Exception ex) + private static async void HandleAuthenticationException(HttpContext context, Exception ex) { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; + context.Response.StatusCode = StatusCodes.Status400BadRequest; await WriteExceptionMessageAsync(context, ex); } private static async void HandleUnauthorizedAccessException(HttpContext context, Exception ex) { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; + context.Response.StatusCode = StatusCodes.Status403Forbidden; await WriteExceptionMessageAsync(context, ex); } - private async void HandleInvalidOperationException(HttpContext context, Exception ex) + private static async void HandleInvalidOperationException(HttpContext context, Exception ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; await WriteExceptionMessageAsync(context, ex); } + + private static async void HandleNotChangedException(HttpContext context, Exception ex) + { + context.Response.StatusCode = StatusCodes.Status204NoContent; + } private static async Task WriteExceptionMessageAsync(HttpContext context, Exception ex) { diff --git a/src/Api/Program.cs b/src/Api/Program.cs index 42a37f8f..9c5fb823 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -2,7 +2,6 @@ using Api.Extensions; using Application; using Infrastructure; -using Infrastructure.Persistence; using Serilog; var builder = WebApplication.CreateBuilder(args); @@ -17,17 +16,12 @@ builder.Services.AddApplicationServices(); builder.Services.AddInfrastructureServices(builder.Configuration); - builder.Services.AddApiServices(); - + builder.Services.AddApiServices(builder.Configuration); var app = builder.Build(); - app.UseInfrastructure(); + app.UseInfrastructure(builder.Configuration); - app.MigrateDatabase((context, _) => - { - ApplicationDbContextSeed.Seed(context, builder.Configuration, Log.Logger).Wait(); - }) - .Run(); + app.Run(); } catch (Exception ex) { diff --git a/src/Api/Services/BackgroundWorkers.cs b/src/Api/Services/BackgroundWorkers.cs new file mode 100644 index 00000000..70c1228f --- /dev/null +++ b/src/Api/Services/BackgroundWorkers.cs @@ -0,0 +1,78 @@ +using Application.Common.Interfaces; +using Domain.Statuses; +using Infrastructure.Identity.Authorization; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Api.Services; + +public class BackgroundWorkers : BackgroundService +{ + private readonly IServiceProvider _serviceProvider; + + public BackgroundWorkers(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var workers = new List + { + DisposeExpiredEntries(TimeSpan.FromSeconds(10), stoppingToken), + DisposeExpiredPermissions(TimeSpan.FromSeconds(10), stoppingToken), + HandleOverdueRequest(TimeSpan.FromMinutes(2), stoppingToken), + }; + + await Task.WhenAll(workers.ToArray()); + } + } + + private async Task DisposeExpiredEntries(TimeSpan delay, CancellationToken stoppingToken) + { + var expiryLocalDateTime = LocalDateTime.FromDateTime(DateTime.Now).PlusDays(-30); + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var entries = context.Entries.Where(x => + !EF.Functions.ILike(x.Path, "/%") && x.LastModified!.Value < expiryLocalDateTime); + context.Entries.RemoveRange(entries); + await context.SaveChangesAsync(stoppingToken); + await Task.Delay(delay, stoppingToken); + } + + private async Task DisposeExpiredPermissions(TimeSpan delay, CancellationToken stoppingToken) + { + 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(delay, stoppingToken); + } + + private async Task HandleOverdueRequest(TimeSpan delay, CancellationToken stoppingToken) + { + var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var overdueRequests = context.Borrows + .Where(x => x.Status != BorrowRequestStatus.Overdue + && x.Status == BorrowRequestStatus.CheckedOut + && x.DueTime < localDateTimeNow) + .ToList(); + + foreach (var request in overdueRequests) + { + request.Status = BorrowRequestStatus.Overdue; + } + context.Borrows.UpdateRange(overdueRequests); + + await context.SaveChangesAsync(stoppingToken); + await Task.Delay(delay, stoppingToken); + } +} \ No newline at end of file diff --git a/src/Api/Services/CurrentUserService.cs b/src/Api/Services/CurrentUserService.cs index 34a4b5be..143498a9 100644 --- a/src/Api/Services/CurrentUserService.cs +++ b/src/Api/Services/CurrentUserService.cs @@ -1,30 +1,38 @@ using System.IdentityModel.Tokens.Jwt; using Application.Common.Interfaces; -using Application.Identity; +using Domain.Entities; +using Microsoft.EntityFrameworkCore; 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(JwtRegisteredClaimNames.Sub)); + .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) { @@ -34,22 +42,66 @@ public string GetRole() return user.Role; } - public string? GetDepartment() + public Guid GetDepartmentId() + { + 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(JwtRegisteredClaimNames.Sub)); + .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 + .Include(x => x.Department) + .FirstOrDefault(x => x.Username.Equals(userName)); if (user is null) { throw new UnauthorizedAccessException(); } - return user.Department?.Name; + return user; + } + + public Guid? GetCurrentRoomForStaff() + { + 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 = _dbContext.Staffs + .Include(x => x.User) + .Include(x => x.Room) + .FirstOrDefault(x => x.Id == userId); + + return staff?.Room?.Id; + } + + public Guid? GetCurrentDepartmentForStaff() + { + 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 = _dbContext.Staffs + .Include(x => x.User) + .Include(x => x.Room) + .FirstOrDefault(x => x.Id == userId); + + return staff?.Room?.DepartmentId; } } \ No newline at end of file diff --git a/src/Api/appsettings.Development.json b/src/Api/appsettings.Development.json index 41b82277..7d9b8098 100644 --- a/src/Api/appsettings.Development.json +++ b/src/Api/appsettings.Development.json @@ -9,6 +9,21 @@ "TokenLifetime": "00:20:00", "RefreshTokenLifetimeInDays": 3 }, + "SecuritySettings": { + "Pepper": "1f952d7238f35083abc3d6bf28410702c65f54afc0be29af7f1c89f5859d1d53" + }, + + "MailSettings": { + "ClientUrl": "https://send.api.mailtrap.io/api/send", + "Token": "745f040659edff0ce87b545567da72d2", + "SenderName": "ProFile", + "SenderEmail": "profile@ezarp.dev", + "TemplateUuids": { + "ResetPassword": "9d6a8f25-65e9-4819-be7d-106ce077acf1", + "ShareEntry": "ad69df89-885a-48fb-b8f6-6d06af1a54e3", + "Request": "bce7e60c-d848-4f80-af96-cccd264dcc32" + } + }, "Seed": true, "Serilog" : { "MinimumLevel" : { @@ -17,8 +32,48 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.Authentication": "Debug", - "System": "Warning" + "System": "Warning", + "Infrastructure.Identity.Authentication": "Warning" } - } + }, + "WriteTo": [ + { + "Name": "PostgreSQL", + "Args": { + "connectionString": "Server=postgres;Port=5432;Database=mydb;User ID=profiletest;Password=supasecured", + "tableName": "Logs", + "schemaName": null, + "needAutoCreateTable": true, + "loggerColumnOptions": { + "Id": "IdAutoIncrement", + "Template": "Message", + "Time": "Timestamp", + "Event": "LogEvent", + "Level": "LevelAsText", + "Message": "RenderedMessage" + }, + "loggerPropertyColumnOptions": { + "ObjectType": { + "Name": "ObjectType", + "Format": "{0}", + "WriteMethod": "Raw", + "DbType": "Text" + }, + "ObjectId": { + "Name": "ObjectId", + "Format": "{0}", + "WriteMethod": "Raw", + "DbType": "Uuid" + }, + "UserId": { + "Name": "UserId", + "Format": "{0}", + "WriteMethod": "Raw", + "DbType": "Uuid" + } + } + } + } + ] } -} +} \ No newline at end of file diff --git a/src/Api/appsettings.Testing.json b/src/Api/appsettings.Testing.json new file mode 100644 index 00000000..aafebb14 --- /dev/null +++ b/src/Api/appsettings.Testing.json @@ -0,0 +1,23 @@ +{ + "JweSettings": { + "SigningKeyId": "4bd28be8eac5414fb01c5cbe343b50144bd2", + "EncryptionKeyId": "4bd28be8eac5414fb01c5cbe343b5014", + "TokenLifetime": "00:20:00", + "RefreshTokenLifetimeInDays": 3 + }, + "SecuritySettings": { + "Pepper": "1f952d7238f35083abc3d6bf28410702c65f54afc0be29af7f1c89f5859d1d53" + }, + "Seed": true, + "Serilog" : { + "MinimumLevel" : { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.AspNetCore.Authentication": "Debug", + "System": "Warning" + } + } + } +} diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index ecc9e09f..8853c8ca 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -11,7 +11,11 @@ + + + + @@ -19,8 +23,4 @@ - - - - diff --git a/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs b/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs new file mode 100644 index 00000000..60a9e295 --- /dev/null +++ b/src/Application/Borrows/Commands/ApproveOrRejectBorrowRequest.cs @@ -0,0 +1,146 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Enums; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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)); + + 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; + + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentUserId)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogApproveBorrowRequest(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + } + + if (request.Decision.IsRejection()) + { + borrowRequest.Status = BorrowRequestStatus.Rejected; + + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentUserId)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogRejectBorrowRequest(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + } + + borrowRequest.StaffReason = request.StaffReason; + borrowRequest.LastModified = localDateTimeNow; + borrowRequest.LastModifiedBy = request.CurrentUserId; + + 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/BorrowDocument.cs b/src/Application/Borrows/Commands/BorrowDocument.cs new file mode 100644 index 00000000..7c7eddad --- /dev/null +++ b/src/Application/Borrows/Commands/BorrowDocument.cs @@ -0,0 +1,172 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities.Physical; +using Domain.Events; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class BorrowDocument +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.BorrowReason) + .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); + + RuleFor(x => x.BorrowFrom) + .GreaterThan(DateTime.Now).WithMessage("Borrow date cannot be in the past.") + .Must((command, borrowTime) => borrowTime < command.BorrowTo).WithMessage("Due date cannot be before borrow date."); + + RuleFor(x => x.BorrowTo) + .GreaterThan(DateTime.Now).WithMessage("Due date cannot be in the past."); + } + } + + public record Command : IRequest + { + public Guid DocumentId { get; init; } + public Guid BorrowerId { get; init; } + public DateTime BorrowFrom { get; init; } + public DateTime BorrowTo { get; init; } + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IPermissionManager permissionManager, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _permissionManager = permissionManager; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var user = await _context.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == request.BorrowerId, cancellationToken); + if (user is null) + { + throw new KeyNotFoundException("User does not exist."); + } + + if (user.IsActive is false) + { + throw new ConflictException("User is not active."); + } + + if (user.IsActivated is false) + { + throw new ConflictException("User is not activated."); + } + + var document = await _context.Documents + .Include(x => x.Department) + .Include(x => x.Importer) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Available) + { + throw new ConflictException("Document is not available."); + } + + if (document.Department!.Id != user.Department!.Id) + { + throw new ConflictException("User is not allowed to borrow this document."); + } + + // getting out a request of that document which is either not due or overdue + // 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(_dateTimeProvider.DateTimeNow); + var borrowFromTime = LocalDateTime.FromDateTime(request.BorrowFrom); + var borrowToTime = LocalDateTime.FromDateTime(request.BorrowTo); + var existedBorrows = _context.Borrows + .Include(x => x.Borrower) + .Where(x => + x.Document.Id == request.DocumentId + && (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 (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 ((borrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut) + && (borrowFromTime <= borrow.DueTime && borrowToTime >= borrow.BorrowTime)) + { + throw new ConflictException("This document cannot be borrowed."); + } + } + + var entity = new Borrow() + { + Borrower = user, + Document = document, + 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 (document.ImporterId != request.BorrowerId && !isGranted) + { + throw new UnauthorizedAccessException("You don't have permission to borrow this document."); + } + entity.Status = BorrowRequestStatus.Approved; + } + + + var result = await _context.Borrows.AddAsync(entity, cancellationToken); + entity.AddDomainEvent(new RequestCreated($"{user.FirstName} {user.LastName}", "borrow request", "borrow", + document.Title, entity.Id, request.BorrowReason, document.Id)); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties("Request", document.Id, user.Id)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogBorrowDocument(_logger, document.Id.ToString(), result.Entity.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/CancelBorrowRequest.cs b/src/Application/Borrows/Commands/CancelBorrowRequest.cs new file mode 100644 index 00000000..135d05ef --- /dev/null +++ b/src/Application/Borrows/Commands/CancelBorrowRequest.cs @@ -0,0 +1,73 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class CancelBorrowRequest +{ + public record Command : IRequest + { + public Guid CurrentUserId { get; init; } + public Guid BorrowId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 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); + + borrowRequest.Status = BorrowRequestStatus.Cancelled; + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentUserId)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogCancelBorrowRequest(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/CheckoutDocument.cs b/src/Application/Borrows/Commands/CheckoutDocument.cs new file mode 100644 index 00000000..0dd95d0a --- /dev/null +++ b/src/Application/Borrows/Commands/CheckoutDocument.cs @@ -0,0 +1,98 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class CheckoutDocument +{ + public record Command : IRequest + { + public User CurrentStaff { get; init; } = null!; + public Guid BorrowId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 not DocumentStatus.Available) + { + throw new ConflictException("Document is not available."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Approved) + { + 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 result = _context.Borrows.Update(borrowRequest); + _context.Documents.Update(borrowRequest.Document); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentStaff.Id)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogCheckoutDocument(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/ReportFoundDocument.cs b/src/Application/Borrows/Commands/ReportFoundDocument.cs new file mode 100644 index 00000000..1ccb64c9 --- /dev/null +++ b/src/Application/Borrows/Commands/ReportFoundDocument.cs @@ -0,0 +1,106 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class ReportFoundDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid BorrowId { 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 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."); + } + + 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."); + } + + // Staff cant report lost documents from other department + if (staff.Room.Id != borrowRequest.Document.Folder!.Locker.Room.Id) + { + throw new ConflictException("Request cannot be checked out due to different room."); + } + + if (borrowRequest.Document.Status is not DocumentStatus.Lost) + { + throw new ConflictException("Document is not lost."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Lost) + { + throw new ConflictException("Request is not lost."); + } + + // Get borrows for the lost document which are not processable + var borrowsForDocument = _context.Borrows + .Include(x => x.Document) + .Where( x => x.Id != request.BorrowId + && x.Document.Id == borrowRequest.Document.Id + && x.Status == BorrowRequestStatus.NotProcessable); + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + foreach (var borrow in borrowsForDocument) + { + if (borrow.DueTime < localDateTimeNow) + { + borrow.Status = BorrowRequestStatus.Pending; + } + } + + borrowRequest.Status = BorrowRequestStatus.Returned; + borrowRequest.Document.Status = DocumentStatus.Available; + borrowRequest.ActualReturnTime = localDateTimeNow; + + 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/ReportLostDocument.cs b/src/Application/Borrows/Commands/ReportLostDocument.cs new file mode 100644 index 00000000..30552316 --- /dev/null +++ b/src/Application/Borrows/Commands/ReportLostDocument.cs @@ -0,0 +1,98 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Commands; + +public class ReportLostDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + 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) + .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."); + } + + 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."); + } + + // Staff cant report lost documents from other department + if (staff.Room.Id != borrowRequest.Document.Folder!.Locker.Room.Id) + { + throw new ConflictException("Request cannot be checked out due to different room."); + } + + if (borrowRequest.Document.Status is not DocumentStatus.Borrowed) + { + throw new ConflictException("Document is not borrowed."); + } + + if (borrowRequest.Status is not (BorrowRequestStatus.Overdue or BorrowRequestStatus.CheckedOut)) + { + throw new ConflictException("Request cannot be lost."); + } + + // Get borrows for the lost document which is still pending + var borrowsForDocument = _context.Borrows + .Include(x => x.Document) + .Where( x => x.Id != request.BorrowId + && x.Document.Id == borrowRequest.Document.Id + && x.Status == BorrowRequestStatus.Pending); + + foreach (var borrow in borrowsForDocument) + { + borrow.Status = BorrowRequestStatus.NotProcessable; + } + + borrowRequest.Status = BorrowRequestStatus.Lost; + borrowRequest.Document.Status = DocumentStatus.Lost; + 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 new file mode 100644 index 00000000..2ee6da97 --- /dev/null +++ b/src/Application/Borrows/Commands/ReturnDocument.cs @@ -0,0 +1,99 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class ReturnDocument +{ + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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.Document.Id == request.DocumentId + && x.Status == BorrowRequestStatus.CheckedOut, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Document.Status is not DocumentStatus.Borrowed) + { + throw new ConflictException("Document is not borrowed."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.CheckedOut) + { + 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 = localDateTimeNow; + + var result = _context.Borrows.Update(borrowRequest); + _context.Documents.Update(borrowRequest.Document); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentUser.Id)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogReturnDocument(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/UpdateBorrow.cs b/src/Application/Borrows/Commands/UpdateBorrow.cs new file mode 100644 index 00000000..cdbf0d9e --- /dev/null +++ b/src/Application/Borrows/Commands/UpdateBorrow.cs @@ -0,0 +1,124 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class UpdateBorrow +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.BorrowReason) + .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); + + RuleFor(x => x.BorrowFrom) + .GreaterThan(DateTime.Now).WithMessage("Borrow date cannot be in the past.") + .Must((command, borrowTime) => borrowTime < command.BorrowTo).WithMessage("Due date cannot be before borrow date."); + + RuleFor(x => x.BorrowTo) + .GreaterThan(DateTime.Now).WithMessage("Due date cannot be in the past."); + } + } + + 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 BorrowReason { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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("Cannot update borrow request."); + } + + if (borrowRequest.Document.Status is DocumentStatus.Lost) + { + throw new ConflictException("Document is lost."); + } + + 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) + .Where(x => + x.Document.Id == borrowRequest.Document.Id + && x.Id != borrowRequest.Id + && ((x.DueTime > localDateTimeNow) + || x.Status == BorrowRequestStatus.Overdue)); + + var borrowFromTime = LocalDateTime.FromDateTime(request.BorrowFrom); + var borrowToTime = LocalDateTime.FromDateTime(request.BorrowTo); + foreach (var borrow in existedBorrows) + { + if ((borrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut) + && (borrowFromTime <= borrow.DueTime && borrowToTime >= borrow.BorrowTime)) + { + throw new ConflictException("This document cannot be updated."); + } + } + borrowRequest.BorrowTime = borrowFromTime; + borrowRequest.DueTime = borrowToTime; + borrowRequest.BorrowReason = request.BorrowReason; + borrowRequest.LastModified = localDateTimeNow; + borrowRequest.LastModifiedBy = request.CurrentUser.Id; + + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties("BorrowRequest", borrowRequest.Id, request.CurrentUser.Id)) + { + Common.Extensions.Logging.BorrowLogExtensions.LogUpdateBorrow(_logger, borrowRequest.Document.Id.ToString(), borrowRequest.Id.ToString()); + } + + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs new file mode 100644 index 00000000..b691e0ee --- /dev/null +++ b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs @@ -0,0 +1,146 @@ +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 MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +public class GetAllBorrowRequestsPaginated +{ + public record Query : IRequest> + { + 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; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + public string[]? Statuses { 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."); + } + } + + var borrows = _context.Borrows.AsQueryable(); + + borrows = borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(y => y.Department) + .Include(x => x.Document) + .ThenInclude(y => y.Folder) + .ThenInclude(z => z!.Locker) + .ThenInclude(t => t.Room) + .ThenInclude(s => s.Department); + + if (request.CurrentUser.Role.IsEmployee()) + { + + if (request.EmployeeId != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("User can not access this resource"); + } + + if (request.RoomId is not null) + { + 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.RoomId is not null) + { + borrows = borrows.Where(x => x.Document.Folder!.Locker.Room.Id == request.RoomId); + } + + if (request.EmployeeId is not null) + { + borrows = borrows.Where(x => x.Borrower.Id == request.EmployeeId); + } + + if (request.DocumentId is not null) + { + borrows = borrows.Where(x => x.Document.Id == request.DocumentId); + } + + if (request.Statuses is not null) + { + var statuses = request.Statuses.Aggregate(new List(), + (statuses, currentStatus) => + { + if (!Enum.TryParse(currentStatus, true, out var validStatus)) return statuses; + + statuses.Add(validStatus); + return statuses; + }); + borrows = borrows.Where(x => statuses.Contains(x.Status)); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(BorrowDto.Status); + } + 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 borrows.CountAsync(cancellationToken); + var list = await borrows + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + 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/GetAllBorrowRequestsSpecificPaginated.cs b/src/Application/Borrows/Queries/GetAllBorrowRequestsSpecificPaginated.cs new file mode 100644 index 00000000..a645079f --- /dev/null +++ b/src/Application/Borrows/Queries/GetAllBorrowRequestsSpecificPaginated.cs @@ -0,0 +1,79 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +[Obsolete] +public class GetAllBorrowRequestsSpecificPaginated +{ + public record Query : IRequest> + { + public Guid? DocumentId { get; init; } + public Guid? EmployeeId { 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 borrows = _context.Borrows.AsQueryable(); + + borrows = borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(y => y.Department) + .Include(x => x.Document) + .ThenInclude(y => y.Folder) + .ThenInclude(z => z!.Locker) + .ThenInclude(t => t.Room) + .ThenInclude(s => s.Department); + + if (request.EmployeeId is not null) + { + borrows = borrows.Where(x => x.Borrower.Id == request.EmployeeId); + } + + if (request.DocumentId is not null) + { + borrows = borrows.Where(x => x.Document.Id == request.DocumentId); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(BorrowDto.Status); + } + 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 list = await borrows + .Paginate(pageNumber.Value, sizeNumber.Value) + .OrderByCustom(sortBy, sortOrder) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + return new PaginatedList(result, result.Count, pageNumber.Value, sizeNumber.Value); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Queries/GetBorrowRequestById.cs b/src/Application/Borrows/Queries/GetBorrowRequestById.cs new file mode 100644 index 00000000..d747f7c4 --- /dev/null +++ b/src/Application/Borrows/Queries/GetBorrowRequestById.cs @@ -0,0 +1,82 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +public class GetBorrowRequestById +{ + public record Query : IRequest + { + public Guid BorrowId { get; init; } + public User User { get; init; } = null!; + } + 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 borrow = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(y => y.Department) + .Include(x => x.Document) + .ThenInclude(x => x.Folder) + .ThenInclude(x => x.Locker) + .ThenInclude(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + + if (borrow is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (request.User.Role.IsAdmin()) + { + return _mapper.Map(borrow); + } + + if (request.User.Role.IsStaff()) + { + var staff = _context.Staffs + .Include(x => x.Room) + .FirstOrDefault(x => x.Id == request.User.Id); + 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 != borrow.Document.Folder!.Locker.Room.Id ) + { + throw new UnauthorizedAccessException("User can not access this resource."); + } + + return _mapper.Map(borrow); + } + + return borrow.Borrower.Id != request.User.Id + ? throw new UnauthorizedAccessException("User can not access this resource") + : _mapper.Map(borrow); + } + } +} \ No newline at end of file diff --git a/src/Application/Common/Exceptions/NotChangedException.cs b/src/Application/Common/Exceptions/NotChangedException.cs new file mode 100644 index 00000000..3ba19843 --- /dev/null +++ b/src/Application/Common/Exceptions/NotChangedException.cs @@ -0,0 +1,8 @@ +namespace Application.Common.Exceptions; + +public class NotChangedException : Exception +{ + public NotChangedException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/BorrowLogExtensions.cs b/src/Application/Common/Extensions/Logging/BorrowLogExtensions.cs new file mode 100644 index 00000000..cd33de16 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/BorrowLogExtensions.cs @@ -0,0 +1,28 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; + +namespace Application.Common.Extensions.Logging; + +public static partial class BorrowLogExtensions +{ + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.NewBorrowRequest, EventId = Common.Logging.EventId.Add)] + public static partial void LogBorrowDocument(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Cancel, EventId = Common.Logging.EventId.OtherRequestRelated)] + public static partial void LogCancelBorrowRequest(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Checkout, EventId = Common.Logging.EventId.OtherRequestRelated)] + public static partial void LogCheckoutDocument(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Return, EventId = Common.Logging.EventId.OtherRequestRelated)] + public static partial void LogReturnDocument(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Return, EventId = Common.Logging.EventId.Update)] + public static partial void LogUpdateBorrow(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Approve, EventId = Common.Logging.EventId.Approve)] + public static partial void LogApproveBorrowRequest(this ILogger logger, string documentId, string borrowId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Borrow.Reject, EventId = Common.Logging.EventId.Reject)] + public static partial void LogRejectBorrowRequest(this ILogger logger, string documentId, string borrowId); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/DocumentLogExtensions.cs b/src/Application/Common/Extensions/Logging/DocumentLogExtensions.cs new file mode 100644 index 00000000..13e8a07b --- /dev/null +++ b/src/Application/Common/Extensions/Logging/DocumentLogExtensions.cs @@ -0,0 +1,24 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class DocumentLogExtensions +{ + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.NewImport, EventId = EventId.Add)] + public static partial void LogImportDocument(this ILogger logger, string documentId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Delete, EventId = EventId.Remove)] + public static partial void LogDeleteDocument(this ILogger logger, string documentId); + + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Update, EventId = EventId.Update)] + public static partial void LogUpdateDocument(this ILogger logger, string documentId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Grant, EventId = EventId.Update)] + public static partial void LogGrantPermission(this ILogger logger, string permission, string username); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Revoke, EventId = EventId.Update)] + public static partial void LogRevokePermission(this ILogger logger, string permission, string username); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/EntryLogExtension.cs b/src/Application/Common/Extensions/Logging/EntryLogExtension.cs new file mode 100644 index 00000000..d99b237e --- /dev/null +++ b/src/Application/Common/Extensions/Logging/EntryLogExtension.cs @@ -0,0 +1,33 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class EntryLogExtension +{ + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.DownloadDigitalFile, EventId = Common.Logging.EventId.Other)] + public static partial void LogDownLoadFile(this ILogger logger, string username, string fileId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.MoveEntryToBin, EventId = Common.Logging.EventId.Update)] + public static partial void LogMoveEntryToBin(this ILogger logger, string username, string entryId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.ShareEntry, EventId = EventId.OtherRequestRelated)] + public static partial void LogShareEntry(this ILogger logger, string username, string entryId, string sharedUsername); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.UpdateEntry, EventId = EventId.Update)] + public static partial void LogUpdateEntry(this ILogger logger, string username, string entryId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.UploadDigitalFile, EventId = EventId.Add)] + public static partial void LogCreateEntry(this ILogger logger, string username, string entryId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.UploadSharedEntry, EventId = EventId.Add)] + public static partial void LogCreateSharedEntry(this ILogger logger, string username, string entryId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.DeleteBinEntry, EventId = EventId.Remove)] + public static partial void LogDeleteBinEntry(this ILogger logger, string username, string entryId); + + [LoggerMessage(Level = LogLevel.Information, Message = EntryLogMessages.RestoreBinEntry, EventId = EventId.Update)] + public static partial void LogRestoreBinEntry(this ILogger logger, string username, string entryId); +} + diff --git a/src/Application/Common/Extensions/Logging/FolderLogExtension.cs b/src/Application/Common/Extensions/Logging/FolderLogExtension.cs new file mode 100644 index 00000000..e5da414c --- /dev/null +++ b/src/Application/Common/Extensions/Logging/FolderLogExtension.cs @@ -0,0 +1,17 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class FolderLogExtension { + + [LoggerMessage(Level = LogLevel.Information, Message = FolderLogMessage.Add, EventId = EventId.Add)] + public static partial void LogAddFolder(this ILogger logger, string folderId, string lockerId, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = FolderLogMessage.Remove, EventId = EventId.Remove)] + public static partial void LogRemoveFolder(this ILogger logger, string folderId, string lockerId, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = FolderLogMessage.Update, EventId = EventId.Update)] + public static partial void LogUpdateFolder(this ILogger logger, string folderId, string lockerId, string roomId, string departmentName); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/ImportRequestLogExtensions.cs b/src/Application/Common/Extensions/Logging/ImportRequestLogExtensions.cs new file mode 100644 index 00000000..5143dc19 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/ImportRequestLogExtensions.cs @@ -0,0 +1,41 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class ImportRequestLogExtensions { + // Approve or reject document + [LoggerMessage(Level = LogLevel.Information, Message = RequestLogMessages.ApproveImport, EventId = EventId.Approve)] + public static partial void LogApproveImportRequest(this ILogger logger, string requestId); + + [LoggerMessage(Level = LogLevel.Information, Message = RequestLogMessages.RejectImport, EventId = EventId.Reject)] + public static partial void LogRejectImportRequest(this ILogger logger, string requestId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.Approve, EventId = EventId.Approve)] + public static partial void LogApproveImportRequestForDocument(this ILogger logger, string documentId); + + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.Reject, EventId = EventId.Reject)] + public static partial void LogRejectImportRequestForDocument(this ILogger logger, string documentId); + + // Assign document + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.Assign, EventId = EventId.Update)] + public static partial void LogAssignDocument(this ILogger logger, string documentId, string folderId); + + [LoggerMessage(Level = LogLevel.Information, Message = FolderLogMessage.AssignDocument, EventId = EventId.Update)] + public static partial void LogAssignDocumentToFolder(this ILogger logger, string documentId); + + // Check in document + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.Checkin, EventId = EventId.Update)] + public static partial void LogCheckinDocument(this ILogger logger, string documentId); + + [LoggerMessage(Level = LogLevel.Information, Message = RequestLogMessages.CheckInImport, EventId = EventId.Update)] + public static partial void LogCheckinImportRequest(this ILogger logger, string requestId); + + // Request import document + [LoggerMessage(Level = LogLevel.Information, Message = DocumentLogMessages.Import.NewImportRequest, EventId = EventId.Update)] + public static partial void LogAddDocument(this ILogger logger, string requestId); + + [LoggerMessage(Level = LogLevel.Information, Message = RequestLogMessages.AddImportRequest, EventId = EventId.Update)] + public static partial void LogAddDocumentRequest(this ILogger logger, string requestId); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/LockerLogExtension.cs b/src/Application/Common/Extensions/Logging/LockerLogExtension.cs new file mode 100644 index 00000000..db6d4505 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/LockerLogExtension.cs @@ -0,0 +1,17 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class LockerLogExtension { + + [LoggerMessage(Level = LogLevel.Information, Message = LockerLogMessage.Add, EventId = EventId.Add)] + public static partial void LogAddLocker(this ILogger logger, string lockerId, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = LockerLogMessage.Remove, EventId = EventId.Remove)] + public static partial void LogRemoveLocker(this ILogger logger, string lockerId, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = LockerLogMessage.Update, EventId = EventId.Update)] + public static partial void LogUpdateLocker(this ILogger logger, string lockerId, string roomId, string departmentName); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/LoginLogExtension.cs b/src/Application/Common/Extensions/Logging/LoginLogExtension.cs new file mode 100644 index 00000000..0bd03761 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/LoginLogExtension.cs @@ -0,0 +1,11 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class LoginLogExtension +{ + [LoggerMessage(Level = LogLevel.Information, Message = LoginLogMessages.Login, EventId = EventId.Approve)] + public static partial void LogLogin(this ILogger logger, string username, string time); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/RoomLogExtension.cs b/src/Application/Common/Extensions/Logging/RoomLogExtension.cs new file mode 100644 index 00000000..d1cd8f9f --- /dev/null +++ b/src/Application/Common/Extensions/Logging/RoomLogExtension.cs @@ -0,0 +1,16 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class RoomLogExtension { + [LoggerMessage(Level = LogLevel.Information, Message = RoomLogMessage.Add, EventId = EventId.Add)] + public static partial void LogAddRoom(this ILogger logger, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = RoomLogMessage.Remove, EventId = EventId.Remove)] + public static partial void LogRemoveRoom(this ILogger logger, string roomId, string departmentName); + + [LoggerMessage(Level = LogLevel.Information, Message = RoomLogMessage.Update, EventId = EventId.Update)] + public static partial void LogUpdateRoom(this ILogger logger, string roomId, string departmentName); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/StaffLogExtensions.cs b/src/Application/Common/Extensions/Logging/StaffLogExtensions.cs new file mode 100644 index 00000000..071fd548 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/StaffLogExtensions.cs @@ -0,0 +1,13 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; +using EventId = Application.Common.Logging.EventId; + +namespace Application.Common.Extensions.Logging; + +public static partial class StaffLogExtensions { + [LoggerMessage(Level = LogLevel.Information, Message = UserLogMessages.Staff.AssignStaff, EventId = EventId.Add)] + public static partial void LogAssignStaff(this ILogger logger, string staffId, string roomId); + + [LoggerMessage(Level = LogLevel.Information, Message = UserLogMessages.Staff.RemoveFromRoom, EventId = EventId.Remove)] + public static partial void LogRemoveStaffFromRoom(this ILogger logger, string staffId, string roomId); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/Logging/UserLogExtensions.cs b/src/Application/Common/Extensions/Logging/UserLogExtensions.cs new file mode 100644 index 00000000..7dd99cc7 --- /dev/null +++ b/src/Application/Common/Extensions/Logging/UserLogExtensions.cs @@ -0,0 +1,13 @@ +using Application.Common.Messages; +using Microsoft.Extensions.Logging; + +namespace Application.Common.Extensions.Logging; + +public static partial class UserLogExtensions +{ + [LoggerMessage(Level = LogLevel.Information, Message = UserLogMessages.Add)] + public static partial void LogAddUser(this ILogger logger, string username, string email, string role); + + [LoggerMessage(Level = LogLevel.Information, Message = UserLogMessages.Update)] + public static partial void LogUpdateUser(this ILogger logger, string username); +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/QueryableExtensions.cs b/src/Application/Common/Extensions/QueryableExtensions.cs index 0c5c5f2a..351a92be 100644 --- a/src/Application/Common/Extensions/QueryableExtensions.cs +++ b/src/Application/Common/Extensions/QueryableExtensions.cs @@ -1,4 +1,13 @@ using System.Linq.Expressions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Users.Queries; +using AutoMapper; +using Domain.Common; +using Domain.Entities; +using Microsoft.EntityFrameworkCore; namespace Application.Common.Extensions; @@ -9,15 +18,50 @@ public static IQueryable OrderByCustom(this IQueryable(result); } + + public static IQueryable Paginate(this IQueryable items, int page, int size) + { + return items.Skip((page - 1) * size).Take(size); + } + + 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 new file mode 100644 index 00000000..07f9a6e5 --- /dev/null +++ b/src/Application/Common/Extensions/StringExtensions.cs @@ -0,0 +1,31 @@ +using Application.Identity; + +namespace Application.Common.Extensions; + +public static class StringExtensions +{ + public static bool MatchesPropertyName(this string input) + where T : class + { + var type = typeof(T); + var properties = type.GetProperties(); + + 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 128372e2..014115df 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -1,4 +1,6 @@ +using Application.Common.Models; using Domain.Entities; +using Domain.Entities.Digital; using Domain.Entities.Physical; using Microsoft.EntityFrameworkCore; @@ -14,7 +16,15 @@ 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 Files { get; } + public DbSet Entries { get; } + public DbSet EntryPermissions { get; } + + public DbSet Logs { get; } Task SaveChangesAsync(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Application/Common/Interfaces/IAuthDbContext.cs b/src/Application/Common/Interfaces/IAuthDbContext.cs new file mode 100644 index 00000000..49801213 --- /dev/null +++ b/src/Application/Common/Interfaces/IAuthDbContext.cs @@ -0,0 +1,12 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Application.Common.Interfaces; + +public interface IAuthDbContext +{ + public DbSet RefreshTokens { get; } + public DbSet ResetPasswordTokens { 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 ae870b98..a5528245 100644 --- a/src/Application/Common/Interfaces/ICurrentUserService.cs +++ b/src/Application/Common/Interfaces/ICurrentUserService.cs @@ -1,7 +1,13 @@ +using Domain.Entities; + namespace Application.Common.Interfaces; public interface ICurrentUserService { + Guid GetId(); string GetRole(); - string? GetDepartment(); + Guid GetDepartmentId(); + User GetCurrentUser(); + Guid? GetCurrentRoomForStaff(); + Guid? GetCurrentDepartmentForStaff(); } \ No newline at end of file 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/IIdentityService.cs b/src/Application/Common/Interfaces/IIdentityService.cs index 20fab955..03fc7f21 100644 --- a/src/Application/Common/Interfaces/IIdentityService.cs +++ b/src/Application/Common/Interfaces/IIdentityService.cs @@ -1,5 +1,7 @@ using Application.Common.Models; using Application.Users.Queries; +using Domain.Entities; +using OneOf; namespace Application.Common.Interfaces; @@ -7,6 +9,7 @@ public interface IIdentityService { Task Validate(string token, string refreshToken); Task RefreshTokenAsync(string token, string refreshToken); - Task<(AuthenticationResult AuthResult, UserDto UserCredentials)> LoginAsync(string email, string password); - Task LogoutAsync(string token, string refreshToken); + Task> LoginAsync(string email, string password); + Task LogoutAsync(string token, string refreshToken); + Task ResetPassword(string token, string newPassword); } \ No newline at end of file diff --git a/src/Application/Common/Interfaces/IMailService.cs b/src/Application/Common/Interfaces/IMailService.cs new file mode 100644 index 00000000..fd24066d --- /dev/null +++ b/src/Application/Common/Interfaces/IMailService.cs @@ -0,0 +1,10 @@ +using Application.Common.Models; + +namespace Application.Common.Interfaces; + +public interface IMailService +{ + bool SendResetPasswordHtmlMail(string userEmail, string temporaryPassword, string token); + bool SendShareEntryHtmlMail(bool isDirectory, string name, string sharerName, string operation, string ownerName, string email, string path); + bool SendCreateRequestHtmlMail(string userName, string requestType, string operation, string documentName, string reason, Guid documentId, string email); +} \ 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/Interfaces/ISecurityService.cs b/src/Application/Common/Interfaces/ISecurityService.cs new file mode 100644 index 00000000..5cbd6f96 --- /dev/null +++ b/src/Application/Common/Interfaces/ISecurityService.cs @@ -0,0 +1,6 @@ +namespace Application.Common.Interfaces; + +public interface ISecurityService +{ + string Hash(string input, string salt); +} \ No newline at end of file diff --git a/src/Application/Common/Logging/EventId.cs b/src/Application/Common/Logging/EventId.cs new file mode 100644 index 00000000..9846cf9b --- /dev/null +++ b/src/Application/Common/Logging/EventId.cs @@ -0,0 +1,11 @@ +namespace Application.Common.Logging; + +public static class EventId { + public const int Add = 227910; + public const int Remove = 177013; + public const int Update = 366224; + public const int Approve = 372415; + public const int Reject = 342738; + public const int OtherRequestRelated = 1337; + public const int Other = 390009; +} \ No newline at end of file diff --git a/src/Application/Common/Logging/Logging.cs b/src/Application/Common/Logging/Logging.cs new file mode 100644 index 00000000..15222cdd --- /dev/null +++ b/src/Application/Common/Logging/Logging.cs @@ -0,0 +1,27 @@ +using Serilog.Context; + +namespace Application.Common.Logging; + +public class Logging : IDisposable +{ + private IDisposable ObjectType { get; } + private IDisposable ObjectId { get; } + private IDisposable UserId { get; } + + private Logging(string objectType, Guid objectId, Guid userId) + { + ObjectType = LogContext.PushProperty("ObjectType", objectType); + ObjectId = LogContext.PushProperty("ObjectId", objectId); + UserId = LogContext.PushProperty("UserId", userId); + } + + public static Logging PushProperties(string objectType, Guid objectId, Guid userId) + => new(objectType, objectId, userId); + public void Dispose() + { + ObjectType.Dispose(); + ObjectId.Dispose(); + UserId.Dispose(); + GC.SuppressFinalize(this); + } +} \ 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..44c8eb1f --- /dev/null +++ b/src/Application/Common/Messages/DocumentLogMessages.cs @@ -0,0 +1,28 @@ +namespace Application.Common.Messages; + +public static class DocumentLogMessages +{ + public static class Import + { + public const string NewImport = "Imported new document with id {DocumentId}"; + public const string NewImportRequest = "Created new import request"; + public const string Checkin = "Checked in document with id {DocumentId}"; + public const string Approve = "Document with id {DocumentId} is approved to be imported"; + public const string Reject = "Document with id {DocumentId} is rejected to be imported"; + public const string Assign = "Assigned document with id {DocumentId} to folder {FolderId}"; + } + public static class Borrow + { + public const string NewBorrowRequest = "Create new borrow request for document {DocumentId} with id {BorrowId}"; + public const string Cancel = "Cancel borrow request for document {DocumentId} with id {BorrowId}"; + public const string Approve = "Approve borrow request for document {DocumentId} with id {BorrowId}"; + public const string Reject = "Reject borrow request for document {DocumentId} with id {BorrowId}"; + public const string Checkout = "Check out borrow request for document {DocumentId} with id {BorrowId}"; + public const string Return = "Return borrow request for document {DocumentId} with id {BorrowId}"; + public const string UpdateBorrow = "Update borrow request for document {DocumentId} with id {BorrowId}"; + } + public const string Delete = "Delete document with id {DocumentId}"; + public const string Update = "Update document with id {DocumentId} "; + public const string Grant = "Share Permission {Permission} to user {Username}"; + public const string Revoke = "Remove Permission {Permission} from user {Username}"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/EntryLogMessages.cs b/src/Application/Common/Messages/EntryLogMessages.cs new file mode 100644 index 00000000..f4096fc6 --- /dev/null +++ b/src/Application/Common/Messages/EntryLogMessages.cs @@ -0,0 +1,13 @@ +namespace Application.Common.Messages; + +public class EntryLogMessages +{ + public const string DeleteBinEntry = "User with username {Username} delete entry with Id {EntryId}"; + public const string DownloadDigitalFile = "User with username {Username} download file with Id {FileId}"; + public const string MoveEntryToBin = "User with username {Username} move entry with Id {EntryId} to bin"; + public const string RestoreBinEntry = "User with username {Username} restore entry with Id {EntryId} from bin"; + public const string ShareEntry = "User with username {Username} change permission of entry with Id {EntryId} to User with id {SharedUsername}"; + public const string UpdateEntry = "User with username {Username} update entry with Id {EntryId}"; + public const string UploadDigitalFile = "User with username {Username} upload an entry with id {EntryId}"; + public const string UploadSharedEntry = "User with username {Username} upload an entry with id {EntryId}"; +} \ 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..ff1c0f06 --- /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 = "Add folder with Id {FolderId} to locker {LockerId} in room {RoomId} in department {DepartmentName}"; + public const string Update = "Updated folder with Id {FolderId} to locker {LockerId} in room {RoomId} in department {DepartmentName}"; + public const string Remove = "Removed folder with Id {FolderId} to locker {LockerId} in room {RoomId} in department {DepartmentName}"; + public const string AssignDocument = "New document with id {DocumentId} was assigned to this 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..604229b7 --- /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 = "Add locker with id {LockerId} to room {RoomId} in Department {DepartmentName}"; + public const string Update = "Update locker with id {LockerId} to room {RoomId} in Department {DepartmentName}"; + public const string Remove = "Remove locker with id {LockerId} to room {RoomId} in Department {DepartmentName}"; +} \ No newline at end of file diff --git a/src/Application/Common/Messages/LoginLogMessages.cs b/src/Application/Common/Messages/LoginLogMessages.cs new file mode 100644 index 00000000..102e3e4b --- /dev/null +++ b/src/Application/Common/Messages/LoginLogMessages.cs @@ -0,0 +1,6 @@ +namespace Application.Common.Messages; + +public class LoginLogMessages +{ + public const string Login = "User with username {Username} Log in at {Time}"; +} \ 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..2142c060 --- /dev/null +++ b/src/Application/Common/Messages/RequestLogMessages.cs @@ -0,0 +1,11 @@ +namespace Application.Common.Messages; + +public static class RequestLogMessages +{ + public const string AddImportRequest = "Created new import request with id {RequestId}"; + public const string ApproveImport = "Approved import request with id {RequestId}"; + public const string RejectImport = "Rejected import request with id {RequestId}"; + public const string ApproveBorrow = "Rejected borrow request with id {RequestId}"; + public const string RejectBorrow = "Rejected borrow request with id {RequestId}"; + public const string CheckInImport = "Checked in import request with id {RequestId}"; +} \ 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..926be34d --- /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 = "Add room with Id {RoomId} in department {DepartmentName}"; + public const string Update = "Update room with Id {RoomId} in department {DepartmentName}"; + public const string Remove = "Remove room with Id {RoomId} in department {DepartmentName}"; +} \ 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..82182f20 --- /dev/null +++ b/src/Application/Common/Messages/UserLogMessages.cs @@ -0,0 +1,16 @@ +namespace Application.Common.Messages; + +public static class UserLogMessages +{ + public const string Add = "Added user {Username} with email {Email} of role {Role}"; + public const string Update = "Updated user {Username} information"; + public const string Disable = "Disabled user"; + + public static class Staff + { + public const string AddStaff = "Add a new staff with id {StaffId}"; + public const string AssignStaff = "Assign staff {StaffId} to room {RoomId}"; + public const string RemoveFromRoom = "Remove staff {StaffId} from room {RoomId}"; + 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/DashBoard/LargestDriveDto.cs b/src/Application/Common/Models/Dtos/DashBoard/LargestDriveDto.cs new file mode 100644 index 00000000..b7ce4c77 --- /dev/null +++ b/src/Application/Common/Models/Dtos/DashBoard/LargestDriveDto.cs @@ -0,0 +1,10 @@ +using Application.Users.Queries; + +namespace Application.Common.Models.Dtos.DashBoard; + +public class LargestDriveDto +{ + public string Label { get; set; } + public long Value { get; set; } + public UserDto User { get; set; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/DashBoard/MetricResultDto.cs b/src/Application/Common/Models/Dtos/DashBoard/MetricResultDto.cs new file mode 100644 index 00000000..0c98f1e2 --- /dev/null +++ b/src/Application/Common/Models/Dtos/DashBoard/MetricResultDto.cs @@ -0,0 +1,7 @@ +namespace Application.Common.Models.Dtos.DashBoard; + +public class MetricResultDto +{ + public string Label { get; set; } + public int Value { 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 286a4b08..2a22ab44 100644 --- a/src/Application/Common/Models/Dtos/DepartmentDto.cs +++ b/src/Application/Common/Models/Dtos/DepartmentDto.cs @@ -1,10 +1,11 @@ using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; using Domain.Entities; -namespace Application.Users.Queries; +namespace Application.Common.Models.Dtos; -public class DepartmentDto : IMapFrom +public class DepartmentDto : BaseDto, IMapFrom { - public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = null!; } \ 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 new file mode 100644 index 00000000..29e5021f --- /dev/null +++ b/src/Application/Common/Models/Dtos/Digital/EntryDto.cs @@ -0,0 +1,36 @@ +using Application.Common.Mappings; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities.Digital; + +namespace Application.Common.Models.Dtos.Digital; + +public class EntryDto : BaseDto, IMapFrom +{ + public string Name { get; set; } = null!; + public string Path { get; set; } = null!; + public Guid? FileId { get; set; } + public string? FileType { get; set; } + public string? FileExtension { get; set; } + public bool IsDirectory { get; set; } + public long? SizeInBytes { get; set; } + public UserDto Owner { get; set; } = null!; + public DateTime Created { get; set; } + public Guid? CreatedBy { get; set; } + public DateTime? LastModified { get; set; } + public Guid? LastModifiedBy { get; set; } + public UserDto Uploader { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Created, + opt => opt.MapFrom(src => src.Created.ToDateTimeUnspecified())) + .ForMember(dest => dest.LastModified, + opt => opt.MapFrom(src => src.LastModified.Value.ToDateTimeUnspecified())) + .ForMember(dest => dest.FileType, + opt => opt.MapFrom(src => src.File.FileType)) + .ForMember(dest => dest.FileExtension, + opt => opt.MapFrom(src => src.File.FileExtension)); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Digital/EntryPermissionDto.cs b/src/Application/Common/Models/Dtos/Digital/EntryPermissionDto.cs new file mode 100644 index 00000000..9165b3c5 --- /dev/null +++ b/src/Application/Common/Models/Dtos/Digital/EntryPermissionDto.cs @@ -0,0 +1,28 @@ +using Application.Common.Mappings; +using Application.Common.Models.Operations; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; + +namespace Application.Common.Models.Dtos.Digital; + +public class EntryPermissionDto : IMapFrom +{ + public Guid EmployeeId { get; set; } + public Guid EntryId { get; set; } + public bool CanView { get; set; } + public bool CanEdit { get; set; } + public bool IsSharedRoot { get; set; } + + public UserDto Employee { get; set; } = null!; + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.CanView, + opt => opt.MapFrom(src => src.AllowedOperations.Split(',', StringSplitOptions.TrimEntries).Contains(EntryOperation.View.ToString()))) + .ForMember(dest => dest.CanEdit, + opt => opt.MapFrom(src => src.AllowedOperations.Split(',', StringSplitOptions.TrimEntries).Contains(EntryOperation.Edit.ToString()))); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Digital/FileDto.cs b/src/Application/Common/Models/Dtos/Digital/FileDto.cs new file mode 100644 index 00000000..60c6c796 --- /dev/null +++ b/src/Application/Common/Models/Dtos/Digital/FileDto.cs @@ -0,0 +1,11 @@ +using Application.Common.Mappings; +using Domain.Entities.Digital; + +namespace Application.Common.Models.Dtos.Digital; + +public class FileDto : BaseDto, IMapFrom +{ + public string FileType { get; set; } = null!; + public string? FileExtension { get; set; } + public string[] FileData { 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/LogDto.cs b/src/Application/Common/Models/Dtos/LogDto.cs new file mode 100644 index 00000000..eeef15e1 --- /dev/null +++ b/src/Application/Common/Models/Dtos/LogDto.cs @@ -0,0 +1,16 @@ +using Application.Users.Queries; +using NodaTime; + +namespace Application.Common.Models.Dtos; + +public class LogDto { + public int Id { get; set; } + public string? Template { get; set; } + public string? Message { get; set; } + public string? Level { get; set; } + public DateTime? Time { get; set; } + public string? Event { get; set; } + public UserDto? User { get; set; } + public Guid? ObjectId { get; set; } + public string? ObjectType { get; set; } +} \ 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 bb171be9..8199fa36 100644 --- a/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs @@ -5,21 +5,31 @@ namespace Application.Common.Models.Dtos.Physical; -public class BorrowDto : IMapFrom +public class BorrowDto : BaseDto, IMapFrom { - public Guid Id { get; set; } - public UserDto Borrower { get; set; } - public DocumentDto Document { get; set; } + public Guid BorrowerId { get; set; } + public Guid DocumentId { get; set; } public DateTime BorrowTime { get; set; } public DateTime DueTime { get; set; } - public string Reason { get; set; } + public DateTime ActualReturnTime { get; set; } + public string BorrowReason { 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.BorrowerId, + opt => opt.MapFrom(src => src.Borrower.Id)) + .ForMember(dest => dest.DocumentId, + opt => opt.MapFrom(src => src.Document.Id)) + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())) .ForMember(dest => dest.BorrowTime, opt => opt.MapFrom(src => src.BorrowTime.ToDateTimeUnspecified())) .ForMember(dest => dest.DueTime, - opt => opt.MapFrom(src => src.DueTime.ToDateTimeUnspecified())); + opt => opt.MapFrom(src => src.DueTime.ToDateTimeUnspecified())) + .ForMember(dest => dest.ActualReturnTime, + opt => opt.MapFrom(src => src.ActualReturnTime.ToDateTimeUnspecified())); } } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs index 23f0f4ec..8b46f9b5 100644 --- a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs @@ -1,16 +1,27 @@ using Application.Common.Mappings; +using Application.Common.Models.Dtos.Digital; using Application.Users.Queries; +using AutoMapper; using Domain.Entities.Physical; 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!; public DepartmentDto? Department { get; set; } public UserDto? Importer { get; set; } public FolderDto? Folder { get; set; } + public string Status { get; set; } = null!; + public bool IsPrivate { get; set; } + public Guid? FileId { get; set; } + + 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/Documents/Queries/GetAllDocumentsPaginated/DocumentItemDto.cs b/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs similarity index 63% rename from src/Application/Documents/Queries/GetAllDocumentsPaginated/DocumentItemDto.cs rename to src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs index 86cc8533..1b1e6fb9 100644 --- a/src/Application/Documents/Queries/GetAllDocumentsPaginated/DocumentItemDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/DocumentItemDto.cs @@ -1,15 +1,11 @@ using Application.Common.Mappings; -using Application.Common.Models.Dtos.Physical; using Application.Users.Queries; -using AutoMapper; using Domain.Entities.Physical; -namespace Application.Documents.Queries.GetAllDocumentsPaginated; +namespace Application.Common.Models.Dtos.Physical; -[Obsolete] -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/Rooms/Queries/GetEmptyContainersPaginated/EmptyFolderDto.cs b/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs similarity index 81% rename from src/Application/Rooms/Queries/GetEmptyContainersPaginated/EmptyFolderDto.cs rename to src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs index 0398e867..b05091d7 100644 --- a/src/Application/Rooms/Queries/GetEmptyContainersPaginated/EmptyFolderDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/EmptyFolderDto.cs @@ -2,9 +2,9 @@ using AutoMapper; using Domain.Entities.Physical; -namespace Application.Rooms.Queries.GetEmptyContainersPaginated; +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/Rooms/Queries/GetEmptyContainersPaginated/EmptyLockerDto.cs b/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs similarity index 76% rename from src/Application/Rooms/Queries/GetEmptyContainersPaginated/EmptyLockerDto.cs rename to src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs index f9f80d7b..13135db4 100644 --- a/src/Application/Rooms/Queries/GetEmptyContainersPaginated/EmptyLockerDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/EmptyLockerDto.cs @@ -1,13 +1,11 @@ using Application.Common.Mappings; -using Application.Common.Models.Dtos.Physical; using AutoMapper; using Domain.Entities.Physical; -namespace Application.Rooms.Queries.GetEmptyContainersPaginated; +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..c8134c34 --- /dev/null +++ b/src/Application/Common/Models/Dtos/Physical/PermissionDto.cs @@ -0,0 +1,26 @@ +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 UserDto Employee { get; set; } = null!; + + 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 5426161f..ce9be4e2 100644 --- a/src/Application/Common/Models/Dtos/Physical/RoomDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/RoomDto.cs @@ -1,17 +1,24 @@ using Application.Common.Mappings; -using Application.Users.Queries.Physical; +using Application.Users.Queries; using AutoMapper; using Domain.Entities.Physical; 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; } - public string Description { get; set; } - public StaffDto Staff { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } + public Guid? StaffId { get; set; } + public DepartmentDto? Department { get; set; } public int Capacity { get; set; } public int NumberOfLockers { get; set; } public bool IsAvailable { 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/Physical/StaffDto.cs b/src/Application/Common/Models/Dtos/Physical/StaffDto.cs index b4ff5a80..62ec45dc 100644 --- a/src/Application/Common/Models/Dtos/Physical/StaffDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/StaffDto.cs @@ -1,11 +1,19 @@ using Application.Common.Mappings; -using Application.Common.Models.Dtos.Physical; +using Application.Users.Queries; +using AutoMapper; using Domain.Entities.Physical; -namespace Application.Users.Queries.Physical; +namespace Application.Common.Models.Dtos.Physical; -public class StaffDto : IMapFrom +public class StaffDto : BaseDto, IMapFrom { - public UserDto User { get; set; } - public RoomDto Room { get; set; } + public UserDto User { get; set; } = null!; + public RoomDto? Room { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Id, + opt => opt.MapFrom(src => src.User.Id)); + } } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/UserDto.cs b/src/Application/Common/Models/Dtos/UserDto.cs index ccb4ba64..7d5a7c2e 100644 --- a/src/Application/Common/Models/Dtos/UserDto.cs +++ b/src/Application/Common/Models/Dtos/UserDto.cs @@ -1,12 +1,13 @@ using Application.Common.Mappings; +using Application.Common.Models.Dtos; using AutoMapper; using Domain.Entities; +using Domain.Entities.Digital; 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/HtmlMailData.cs b/src/Application/Common/Models/HtmlMailData.cs new file mode 100644 index 00000000..238b3846 --- /dev/null +++ b/src/Application/Common/Models/HtmlMailData.cs @@ -0,0 +1,73 @@ +using System.Text.Json.Serialization; + +namespace Application.Common.Models; + +public class HtmlMailData +{ + [JsonPropertyName("from")] + public From From { get; set; } + [JsonPropertyName("to")] + public To[] To { get; set; } + [JsonPropertyName("template_uuid")] + public string TemplateUuid { get; set; } + [JsonPropertyName("template_variables")] + public object TemplateVariables { get; set; } +} + +public class From +{ + [JsonPropertyName("email")] + public string Email { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } +} + +public class To +{ + [JsonPropertyName("email")] + public string Email { get; set; } +} + +public class ResetPasswordTemplateVariables +{ + [JsonPropertyName("user_email")] + public string UserEmail { get; set; } + [JsonPropertyName("token")] + public string Token { get; set; } + [JsonPropertyName("user_password")] + public string UserPassword { get; set; } +} + +public class ShareEntryTemplateVariables +{ + [JsonPropertyName("entry_type")] + public string EntryType { get; set; } + [JsonPropertyName("entry_name")] + public string EntryName { get; set; } + [JsonPropertyName("sharer_name")] + public string SharerName { get; set; } + [JsonPropertyName("operation")] + public string Operation { get; set; } + [JsonPropertyName("owner_name")] + public string OwnerName { get; set; } + [JsonPropertyName("path")] + public string Path { get; set; } +} + +public class CreateRequestTemplateVariables +{ + [JsonPropertyName("user_name")] + public string UserName { get; set; } + [JsonPropertyName("request_type")] + public string RequestType { get; set; } + [JsonPropertyName("operation")] + public string Operation { get; set; } + [JsonPropertyName("document_name")] + public string DocumentName { get; set; } + [JsonPropertyName("reason")] + public string Reason { get; set; } + [JsonPropertyName("path")] + public string Path { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/ItemResult.cs b/src/Application/Common/Models/ItemResult.cs new file mode 100644 index 00000000..0b04fa49 --- /dev/null +++ b/src/Application/Common/Models/ItemResult.cs @@ -0,0 +1,11 @@ +namespace Application.Common.Models; + + +public class ItemsResult +{ + public ItemsResult(IEnumerable items) + { + Items = items; + } + public IEnumerable Items { get; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Log.cs b/src/Application/Common/Models/Log.cs new file mode 100644 index 00000000..ba3e6e61 --- /dev/null +++ b/src/Application/Common/Models/Log.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using NodaTime; + +namespace Application.Common.Models; + +public class Log { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + public string? Template { get; set; } + public string? Message { get; set; } + public string? Level { get; set; } + public Instant? Time { get; set; } + public string? Event { get; set; } + public Guid? UserId { get; set; } + public Guid? ObjectId { get; set; } + public string? ObjectType { get; set; } +} \ No newline at end of file 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/Common/Models/Operations/EntryOperation.cs b/src/Application/Common/Models/Operations/EntryOperation.cs new file mode 100644 index 00000000..48ebef4c --- /dev/null +++ b/src/Application/Common/Models/Operations/EntryOperation.cs @@ -0,0 +1,7 @@ +namespace Application.Common.Models.Operations; + +public enum EntryOperation +{ + View, + Edit, +} \ No newline at end of file diff --git a/src/Application/Dashboards/Queries/GetImportDocuments.cs b/src/Application/Dashboards/Queries/GetImportDocuments.cs new file mode 100644 index 00000000..1c4b12cd --- /dev/null +++ b/src/Application/Dashboards/Queries/GetImportDocuments.cs @@ -0,0 +1,74 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.DashBoard; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Dashboards.Queries; + +public class GetImportDocuments +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.StartDate) + .NotEmpty().WithMessage("Start date can not be empty."); + + RuleFor(x => x.EndDate) + .NotEmpty().WithMessage("End date can not be empty."); + } + } + public record Query : IRequest> + { + public DateTime StartDate { get; init; } + public DateTime EndDate { get; init; } + + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + + public QueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var result = new List(); + + var currentDate = request.StartDate.Date; + var endDate = request.EndDate.Date; + + while (currentDate <= endDate) + { + var nextDate = currentDate.AddDays(1); + var label = $"{currentDate:dd-MM} to {nextDate:dd-MM}"; + if (nextDate <= endDate) + { + var date = currentDate; + var importedDocumentsCount = await _context.Documents + .Where(x => x.Created >= LocalDateTime.FromDateTime(date) + && x.Created <= LocalDateTime.FromDateTime(nextDate) + && x.Status != DocumentStatus.Issued) + .CountAsync(cancellationToken); + + result.Add(new MetricResultDto() + { + Label = label, + Value = importedDocumentsCount + }); + } + currentDate = nextDate; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/Dashboards/Queries/GetLoggedInUser.cs b/src/Application/Dashboards/Queries/GetLoggedInUser.cs new file mode 100644 index 00000000..0abd755e --- /dev/null +++ b/src/Application/Dashboards/Queries/GetLoggedInUser.cs @@ -0,0 +1,55 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.DashBoard; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Dashboards.Queries; + +public class GetLoggedInUser +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Date) + .NotEmpty().WithMessage("Date can not be empty."); + } + } + public record Query : IRequest + { + public DateTime Date { get; init; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + + public QueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var currentDate = request.Date.Date; + + var label = $"{currentDate:dd-MM}"; + var loggedinUsersCount = await _context.Logs + .Where(x => x.ObjectType!.Equals("Login") + && x.Time >= Instant.FromDateTimeUtc(currentDate) + && x.Time < Instant.FromDateTimeUtc(currentDate.AddDays(1))) + .CountAsync(cancellationToken); + + return new MetricResultDto() + { + Label = label, + Value = loggedinUsersCount + }; + } + } +} \ No newline at end of file diff --git a/src/Application/Dashboards/Queries/GetUserWithLargestDriveData.cs b/src/Application/Dashboards/Queries/GetUserWithLargestDriveData.cs new file mode 100644 index 00000000..d5a2be1e --- /dev/null +++ b/src/Application/Dashboards/Queries/GetUserWithLargestDriveData.cs @@ -0,0 +1,60 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.DashBoard; +using Application.Users.Queries; +using AutoMapper; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Dashboards.Queries; + +public class GetUserWithLargestDriveData +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Date) + .NotEmpty().WithMessage("Date can not be empty."); + } + } + public record Query : IRequest + { + public DateTime Date { 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 currentDate = request.Date.Date; + var nextDate = currentDate.AddDays(1); + + var usersWithFileData = await _context.Entries + .Where(x => x.FileId != null && x.Created >= LocalDateTime.FromDateTime(currentDate) + && x.Created < LocalDateTime.FromDateTime(nextDate)) + .GroupBy(entry => entry.Uploader) + .Select(group => new LargestDriveDto() + { + User = _mapper.Map(group.Key), + Label = $"{currentDate:dd-MM}", + Value = group.Sum(x => x.File!.FileData.Length) + }) + .OrderByDescending(x => x.Value) + .FirstOrDefaultAsync(cancellationToken); + return usersWithFileData; + } + } +} \ No newline at end of file diff --git a/src/Application/Departments/Commands/AddDepartment.cs b/src/Application/Departments/Commands/AddDepartment.cs new file mode 100644 index 00000000..a057d0d7 --- /dev/null +++ b/src/Application/Departments/Commands/AddDepartment.cs @@ -0,0 +1,50 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Departments.Commands; + +public class AddDepartment +{ + public record Command : IRequest + { + public string Name { get; init; } = null!; + } + + public class AddDepartmentCommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public AddDepartmentCommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var department = await _context.Departments.FirstOrDefaultAsync(x + => x.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()), cancellationToken); + + if (department is not null) + { + throw new ConflictException("Department name already exists."); + } + + var entity = new Department + { + Name = request.Name, + }; + + var result = await _context.Departments.AddAsync(entity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Departments/Commands/CreateDepartment/CreateDepartmentCommand.cs b/src/Application/Departments/Commands/CreateDepartment/CreateDepartmentCommand.cs deleted file mode 100644 index cda6a16d..00000000 --- a/src/Application/Departments/Commands/CreateDepartment/CreateDepartmentCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Users.Queries; -using AutoMapper; -using Domain.Entities; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Departments.Commands.CreateDepartment; - -public record CreateDepartmentCommand : IRequest -{ - public string Name { get; init; } -} - -public class CreateDepartmentCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public CreateDepartmentCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(CreateDepartmentCommand request, CancellationToken cancellationToken) - { - var department = await _context.Departments.FirstOrDefaultAsync(x => x.Name.Equals(request.Name), cancellationToken); - - if (department is not null) - { - throw new ConflictException("Department name already exists"); - } - var entity = new Department - { - Name = request.Name - }; - - var result = await _context.Departments.AddAsync(entity, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Departments/Commands/DeleteDepartment.cs b/src/Application/Departments/Commands/DeleteDepartment.cs new file mode 100644 index 00000000..b744630b --- /dev/null +++ b/src/Application/Departments/Commands/DeleteDepartment.cs @@ -0,0 +1,41 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos; +using Application.Users.Queries; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Departments.Commands; + +public class DeleteDepartment +{ + public record Command : IRequest + { + public Guid DepartmentId { 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 department = await _context.Departments.FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); + + if (department is null) + { + throw new KeyNotFoundException("Department does not exist."); + } + + var result = _context.Departments.Remove(department); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Departments/Commands/DeleteDepartment/DeleteDepartmentCommand.cs b/src/Application/Departments/Commands/DeleteDepartment/DeleteDepartmentCommand.cs deleted file mode 100644 index 93ea7708..00000000 --- a/src/Application/Departments/Commands/DeleteDepartment/DeleteDepartmentCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Users.Queries; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Departments.Commands.DeleteDepartment; - -public record DeleteDepartmentCommand : IRequest -{ - public Guid DepartmentId { get; init; } -} - -public class DeleteDepartmentCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public DeleteDepartmentCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(DeleteDepartmentCommand request, CancellationToken cancellationToken) - { - var department = await _context.Departments.FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); - - if (department is null) - { - throw new KeyNotFoundException("Department does not exist"); - } - - var result = _context.Departments.Remove(department); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Departments/Queries/GetAllDepartments.cs b/src/Application/Departments/Queries/GetAllDepartments.cs new file mode 100644 index 00000000..c4ca0e9c --- /dev/null +++ b/src/Application/Departments/Queries/GetAllDepartments.cs @@ -0,0 +1,34 @@ +using System.Collections.ObjectModel; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Departments.Queries; + +public class GetAllDepartments +{ + public record Query : IRequest>; + + 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 departments = await _context.Departments + .ToListAsync(cancellationToken); + var result = new ItemsResult(_mapper.Map>(departments)); + return result; + } + } +} diff --git a/src/Application/Departments/Queries/GetAllDepartments/GetAllDepartmentsQuery.cs b/src/Application/Departments/Queries/GetAllDepartments/GetAllDepartmentsQuery.cs deleted file mode 100644 index a0af15e1..00000000 --- a/src/Application/Departments/Queries/GetAllDepartments/GetAllDepartmentsQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.ObjectModel; -using Application.Common.Interfaces; -using Application.Users.Queries; -using AutoMapper; -using Domain.Entities; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Departments.Queries.GetAllDepartments; - -public record GetAllDepartmentsQuery : IRequest>; - -public class GetAllDepartmentsQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public GetAllDepartmentsQueryHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task> Handle(GetAllDepartmentsQuery request, CancellationToken cancellationToken) - { - var departments = await _context.Departments.ToListAsync(cancellationToken); - var result = new ReadOnlyCollection(_mapper.Map>(departments)); - return result; - } -} \ No newline at end of file diff --git a/src/Application/Departments/Queries/GetDepartmentById.cs b/src/Application/Departments/Queries/GetDepartmentById.cs new file mode 100644 index 00000000..0073a6e0 --- /dev/null +++ b/src/Application/Departments/Queries/GetDepartmentById.cs @@ -0,0 +1,49 @@ +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; + +namespace Application.Departments.Queries; + +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 + { + 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.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) + { + throw new KeyNotFoundException("Department does not exist."); + } + + return _mapper.Map(department); + } + } + +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/DeleteDocument.cs b/src/Application/Documents/Commands/DeleteDocument.cs new file mode 100644 index 00000000..6331ff18 --- /dev/null +++ b/src/Application/Documents/Commands/DeleteDocument.cs @@ -0,0 +1,66 @@ +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Documents.Commands; + +public class DeleteDocument +{ + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include( x => x.Folder) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.Folder is not null) + { + document.Folder.NumberOfDocuments -= 1; + _context.Folders.Update(document.Folder); + } + + var result = _context.Documents.Remove(document); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Document), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogDeleteDocument(result.Entity.Id.ToString()); + } + + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/ImportDocument.cs b/src/Application/Documents/Commands/ImportDocument.cs new file mode 100644 index 00000000..9a3ac6ee --- /dev/null +++ b/src/Application/Documents/Commands/ImportDocument.cs @@ -0,0 +1,130 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using Application.ImportRequests; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Documents.Commands; + +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + if (importer is null) + { + 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 + && x.Importer.Id == request.ImporterId); + if (document is not null) + { + throw new ConflictException($"Document title already exists for user {importer.LastName}."); + } + + var folder = await _context.Folders + .Include(x => x.Locker) + .ThenInclude(y => y.Room) + .FirstOrDefaultAsync(x => x.Id == request.FolderId, cancellationToken); + if (folder is null) + { + throw new KeyNotFoundException("Folder does not exist."); + } + + if (folder.Capacity == folder.NumberOfDocuments) + { + 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(), + Description = request.Description?.Trim(), + DocumentType = request.DocumentType.Trim(), + Importer = importer, + Department = importer.Department, + Folder = folder, + Status = DocumentStatus.Available, + IsPrivate = request.IsPrivate, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + var result = await _context.Documents.AddAsync(entity, cancellationToken); + folder.NumberOfDocuments += 1; + _context.Folders.Update(folder); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Document), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogImportDocument(result.Entity.Id.ToString()); + } + using (Logging.PushProperties(nameof(Folder), folder.Id, request.CurrentUser.Id)) + { + _logger.LogAssignDocumentToFolder(result.Entity.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/ImportDocument/ImportDocumentCommand.cs b/src/Application/Documents/Commands/ImportDocument/ImportDocumentCommand.cs deleted file mode 100644 index 373e7202..00000000 --- a/src/Application/Documents/Commands/ImportDocument/ImportDocumentCommand.cs +++ /dev/null @@ -1,73 +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.Documents.Commands.ImportDocument; - -public record ImportDocumentCommand : IRequest -{ - public string Title { get; init; } - public string? Description { get; init; } - public string DocumentType { get; init; } - public Guid ImporterId { get; init; } - public Guid FolderId { get; init; } -} - -public class ImportDocumentCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public ImportDocumentCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(ImportDocumentCommand request, CancellationToken cancellationToken) - { - var importer = await _context.Users - .Include(x => x.Department) - .FirstOrDefaultAsync(x => x.Id == request.ImporterId, cancellationToken); - if (importer is null) - { - throw new KeyNotFoundException("User does not exist"); - } - - var document = _context.Documents.FirstOrDefault(x => - x.Title.Equals(request.Title) - && x.Importer != null - && x.Importer.Id == request.ImporterId); - if (document is not null) - { - throw new ConflictException($"Document title already exists for user {importer.LastName}"); - } - - var folder = await _context.Folders - .FirstOrDefaultAsync(x => x.Id == request.FolderId, cancellationToken); - if (folder is null) - { - throw new KeyNotFoundException("Folder does not exist"); - } - - var entity = new Document() - { - Title = request.Title.Trim(), - Description = request.Description?.Trim(), - DocumentType = request.DocumentType.Trim(), - Importer = importer, - Department = importer.Department, - Folder = folder - }; - - var result = await _context.Documents.AddAsync(entity, cancellationToken); - folder.NumberOfDocuments += 1; - _context.Folders.Update(folder); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Documents/Commands/ShareDocument.cs b/src/Application/Documents/Commands/ShareDocument.cs new file mode 100644 index 00000000..9fd5e7bb --- /dev/null +++ b/src/Application/Documents/Commands/ShareDocument.cs @@ -0,0 +1,154 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext applicationDbContext, IMapper mapper, IPermissionManager permissionManager, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _applicationDbContext = applicationDbContext; + _mapper = mapper; + _permissionManager = permissionManager; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + + await HandlePermissionGrantOrRevoke(request.CanRead, document, DocumentOperation.Read, user, request.ExpiryDate.ToLocalTime(), request.CurrentUser.Id, cancellationToken); + await HandlePermissionGrantOrRevoke(request.CanBorrow, document, DocumentOperation.Borrow, user, request.ExpiryDate.ToLocalTime(), request.CurrentUser.Id, cancellationToken); + + await _applicationDbContext.SaveChangesAsync(cancellationToken); + return _mapper.Map(document); + } + + private async Task HandlePermissionGrantOrRevoke( + bool canPerformAction, + Document document, + DocumentOperation operation, + User user, + DateTime expiryDate, + Guid currentUserId, + CancellationToken cancellationToken) + { + var isGranted = _permissionManager.IsGranted(document.Id, operation, user.Id); + + if (canPerformAction && !isGranted) + { + await GrantPermission(document, operation, user, expiryDate, currentUserId, cancellationToken); + } + + if (!canPerformAction && isGranted) + { + await RevokePermission(document, operation, user, currentUserId, cancellationToken); + } + } + + private async Task GrantPermission( + Document document, + DocumentOperation operation, + User user, + DateTime expiryDate, + Guid currentUserId, + CancellationToken cancellationToken) + { + await _permissionManager.GrantAsync(document, operation, new[] { user }, expiryDate, cancellationToken); + + // log + using (Logging.PushProperties(nameof(Document), document.Id, currentUserId)) + { + switch(operation) + { + case DocumentOperation.Read: + _logger.LogGrantPermission(DocumentOperation.Read.ToString(), user.Username); + break; + case DocumentOperation.Borrow: + _logger.LogGrantPermission(DocumentOperation.Borrow.ToString(), user.Username); + break; + } + } + } + + private async Task RevokePermission( + Document document, + DocumentOperation operation, + User user, + Guid currentUserId, + CancellationToken cancellationToken) + { + await _permissionManager.RevokeAsync(document.Id, operation, new[] { user.Id }, cancellationToken); + + // log + using (Logging.PushProperties(nameof(Document), document.Id, currentUserId)) + { + switch(operation) + { + case DocumentOperation.Read: + _logger.LogRevokePermission(DocumentOperation.Read.ToString(), user.Username); + break; + case DocumentOperation.Borrow: + _logger.LogRevokePermission(DocumentOperation.Borrow.ToString(), user.Username); + break; + } + } + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/UpdateDocument.cs b/src/Application/Documents/Commands/UpdateDocument.cs new file mode 100644 index 00000000..c67a6cdd --- /dev/null +++ b/src/Application/Documents/Commands/UpdateDocument.cs @@ -0,0 +1,122 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Documents.Commands; + +public class UpdateDocument +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("Title is required.") + .MaximumLength(64).WithMessage("Title cannot exceed 64 characters."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); + + RuleFor(x => x.DocumentType) + .NotEmpty().WithMessage("DocumentType is required.") + .MaximumLength(64).WithMessage("DocumentType cannot exceed 64 characters."); + } + } + + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 == 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) + { + var titleExisted = await _context.Documents + .Include(x => x.Importer) + .AnyAsync(x => + x.Title.Trim().ToLower().Equals(request.Title.Trim().ToLower()) + && x.Id != document.Id + && 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 result = _context.Documents.Update(document); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Document), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogUpdateDocument(result.Entity.Id.ToString()); + } + + 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/Commands/UpdateDocument/UpdateDocumentCommand.cs b/src/Application/Documents/Commands/UpdateDocument/UpdateDocumentCommand.cs deleted file mode 100644 index 96e9dc8d..00000000 --- a/src/Application/Documents/Commands/UpdateDocument/UpdateDocumentCommand.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Application.Common.Models.Dtos.Physical; -using MediatR; - -namespace Application.Documents.Commands.UpdateDocument; - -public record UpdateDocumentCommand : IRequest -{ - public Guid Id { get; set; } - public string? Title { get; set; } - public string? Description { get; set; } - public string? DocumentType { get; set; } - public Guid? Department { get; set; } - public Guid? Importer { get; set; } - public Guid? Folder { get; set; } -} \ No newline at end of file diff --git a/src/Application/Documents/Commands/UploadFileToDocument.cs b/src/Application/Documents/Commands/UploadFileToDocument.cs new file mode 100644 index 00000000..75b56197 --- /dev/null +++ b/src/Application/Documents/Commands/UploadFileToDocument.cs @@ -0,0 +1,88 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Commands; + +public abstract class UploadFileToDocument +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + public MemoryStream FileData { get; init; } = null!; + public string FileType { get; init; } = null!; + public string FileExtension { get; init; } = null!; + } + + 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 document = await _context.Documents + .Include(x => x.File) + .Include(x => x.Importer) + .Include(x => x.Department) + .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 (request.CurrentUser.Role.IsEmployee() && document.ImporterId != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("User cannot upload file to this document."); + } + + if (request.CurrentUser.Role.IsStaff() && document.Department!.Id != request.CurrentUser.Department!.Id) + { + throw new UnauthorizedAccessException("User cannot upload file to this document."); + } + + if (document.File is not null) + { + _context.Files.Remove(document.File); + } + + if (document.Status is DocumentStatus.Lost ) + { + throw new ConflictException("This document cannot be uploaded to."); + } + + var file = new FileEntity + { + FileData = request.FileData.ToArray(), + FileType = request.FileType!, + FileExtension = request.FileExtension, + }; + var fileEntity = await _context.Files.AddAsync(file, cancellationToken); + + document.File = fileEntity.Entity; + document.FileId = fileEntity.Entity.Id; + + var result = _context.Documents.Update(document); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/EventHandlers/RequestCreatedHandler.cs b/src/Application/Documents/EventHandlers/RequestCreatedHandler.cs new file mode 100644 index 00000000..fe2cc6ea --- /dev/null +++ b/src/Application/Documents/EventHandlers/RequestCreatedHandler.cs @@ -0,0 +1,37 @@ +using Application.Common.Interfaces; +using Domain.Events; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.EventHandlers; + +public class RequestCreatedHandler : INotificationHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMailService _mailService; + + public RequestCreatedHandler(IMailService mailService, IApplicationDbContext context) + { + _mailService = mailService; + _context = context; + } + + public async Task Handle(RequestCreated notification, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == notification.DocumentId, cancellationToken); + + var departmentId = document!.Department!.Id; + + var staff = await _context.Staffs + .Include(x => x.User) + .FirstOrDefaultAsync(x => x.Room!.DepartmentId == departmentId, cancellationToken); + + if (staff is not null) + { + _mailService.SendCreateRequestHtmlMail(notification.UserName, notification.RequestType, notification.Operation, + notification.DocumentTitle, notification.Reason, notification.RequestId, staff.User.Email); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/DownloadDocumentFile.cs b/src/Application/Documents/Queries/DownloadDocumentFile.cs new file mode 100644 index 00000000..e16a2e7e --- /dev/null +++ b/src/Application/Documents/Queries/DownloadDocumentFile.cs @@ -0,0 +1,64 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public abstract class DownloadDocumentFile +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + } + + public class Result + { + public string FileName { get; set; } = null!; + public string FileType { get; set; } = null!; + public byte[] FileData { get; set; } = null!; + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + + public QueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.File) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.ImporterId != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (document.File is null) + { + throw new ConflictException("Document does not have a digital file."); + } + + var result = new Result + { + FileName = $"{document.Title}.{document.File.FileExtension}", + FileType = document.File.FileType, + FileData = document.File.FileData, + }; + + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllDocumentTypes.cs b/src/Application/Documents/Queries/GetAllDocumentTypes.cs new file mode 100644 index 00000000..41e53d1b --- /dev/null +++ b/src/Application/Documents/Queries/GetAllDocumentTypes.cs @@ -0,0 +1,26 @@ +using System.Collections.ObjectModel; +using Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllDocumentTypes +{ + public record Query : IRequest>; + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + + public QueryHandler(IApplicationDbContext context) + { + _context = context; + } + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + return new ReadOnlyCollection(await _context.Documents.Select(x => x.DocumentType).Distinct() + .ToListAsync(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..d325c437 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllDocumentsForEmployeePaginated.cs @@ -0,0 +1,103 @@ +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) + .Where(x => x.Status != DocumentStatus.Issued); + + 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 new file mode 100644 index 00000000..40ae1e6d --- /dev/null +++ b/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs @@ -0,0 +1,177 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +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; + +namespace Application.Documents.Queries; + +public class GetAllDocumentsPaginated +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.RoomId) + .Must((query, roomId) => roomId is null + ? query.LockerId is null && query.FolderId is null + : query.LockerId is not null || query.FolderId is null).WithMessage("Container orientation is not consistent"); + } + } + + 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; } + 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 string? Role { 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) + { + 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; + var folderExists = request.FolderId is not null; + + documents = documents + .Include(x => x.Department) + .Include(x => x.Folder) + .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) + { + var folder = await _context.Folders + .Include(x => x.Locker) + .ThenInclude(y => y.Room) + .FirstOrDefaultAsync(x => x.Id == request.FolderId + && x.IsAvailable, cancellationToken); + if (folder is null) + { + throw new KeyNotFoundException("Folder does not exist."); + } + + if (folder.Locker.Id != request.LockerId + || folder.Locker.Room.Id != request.RoomId) + { + throw new ConflictException("Either locker or room does not match folder."); + } + + documents = documents + .Where(x => x.Folder!.Id == request.FolderId); + } + else if (lockerExists) + { + var locker = await _context.Lockers + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.LockerId + && x.IsAvailable, cancellationToken); + if (locker is null) + { + throw new KeyNotFoundException("Locker does not exist."); + } + + if (locker.Room.Id != request.RoomId) + { + throw new ConflictException("Room does not match locker."); + } + + documents = documents.Where(x => x.Folder!.Locker.Id == request.LockerId); + } + else if (roomExists) + { + 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."); + } + + documents = documents.Where(x => x.Folder!.Locker.Room.Id == request.RoomId); + } + + 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/GetAllDocumentsPaginatedQuery.cs b/src/Application/Documents/Queries/GetAllDocumentsPaginated/GetAllDocumentsPaginatedQuery.cs deleted file mode 100644 index ff2de9ad..00000000 --- a/src/Application/Documents/Queries/GetAllDocumentsPaginated/GetAllDocumentsPaginatedQuery.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Extensions; -using Application.Common.Interfaces; -using Application.Common.Mappings; -using Application.Common.Models; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using AutoMapper.QueryableExtensions; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Documents.Queries.GetAllDocumentsPaginated; - -public record GetAllDocumentsPaginatedQuery : IRequest> -{ - public Guid? RoomId { get; init; } - public Guid? LockerId { get; init; } - public Guid? FolderId { get; init; } - public int? Page { get; init; } - public int? Size { get; init; } - public string? SortBy { get; init; } - public string? SortOrder { get; init; } -} - -public class GetAllDocumentsPaginatedQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public GetAllDocumentsPaginatedQueryHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task> Handle(GetAllDocumentsPaginatedQuery request, - CancellationToken cancellationToken) - { - var documents = _context.Documents.AsQueryable(); - var roomExists = request.RoomId is not null; - var lockerExists = request.LockerId is not null; - var folderExists = request.FolderId is not null; - - documents = documents.Include(x => x.Department); - - if (folderExists) - { - var folder = await _context.Folders - .Include(x => x.Locker) - .ThenInclude(y => y.Room) - .FirstOrDefaultAsync(x => x.Id == request.FolderId - && x.IsAvailable, cancellationToken); - if (folder is null) - { - throw new KeyNotFoundException("Folder does not exist."); - } - - if (folder.Locker.Id != request.LockerId - || folder.Locker.Room.Id != request.RoomId) - { - throw new ConflictException("Either locker or room does not match folder."); - } - - documents = documents - .Where(x => x.Folder!.Id == request.FolderId); - } - else if (lockerExists) - { - var locker = await _context.Lockers - .Include(x => x.Room) - .FirstOrDefaultAsync(x => x.Id == request.LockerId - && x.IsAvailable, cancellationToken); - if (locker is null) - { - throw new KeyNotFoundException("Locker does not exist."); - } - - if (locker.Room.Id != request.RoomId) - { - throw new ConflictException("Room does not match locker."); - } - - documents = documents.Where(x => x.Folder!.Locker.Id == request.LockerId); - } - else if (roomExists) - { - 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."); - } - - documents = documents.Where(x => x.Folder!.Locker.Room.Id == request.RoomId); - } - - var sortBy = request.SortBy ?? nameof(DocumentDto.Id); - var sortOrder = request.SortOrder ?? "asc"; - var pageNumber = request.Page ?? 1; - var sizeNumber = request.Size ?? 5; - var result = await documents - .ProjectTo(_mapper.ConfigurationProvider) - .OrderByCustom(sortBy, sortOrder) - .PaginatedListAsync(pageNumber, sizeNumber); - - return result; - } -} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetAllDocumentsPaginated/GetAllDocumentsPaginatedQueryValidator.cs b/src/Application/Documents/Queries/GetAllDocumentsPaginated/GetAllDocumentsPaginatedQueryValidator.cs deleted file mode 100644 index d95c7e3d..00000000 --- a/src/Application/Documents/Queries/GetAllDocumentsPaginated/GetAllDocumentsPaginatedQueryValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FluentValidation; - -namespace Application.Documents.Queries.GetAllDocumentsPaginated; - -public class GetAllDocumentsPaginatedQueryValidator : AbstractValidator -{ - public GetAllDocumentsPaginatedQueryValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.RoomId) - .Must((query, roomId) => - { - if (roomId is null) - { - return query.LockerId is null && query.FolderId is null; - } - - if (query.LockerId is null) - { - return query.FolderId is null; - } - - return true; - }).WithMessage("Container orientation is not consistent"); - } -} \ 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/GetAllSharedUsersOfDocumentPaginated.cs b/src/Application/Documents/Queries/GetAllSharedUsersOfDocumentPaginated.cs new file mode 100644 index 00000000..654aae24 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllSharedUsersOfDocumentPaginated.cs @@ -0,0 +1,79 @@ +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 MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllSharedUsersOfDocumentPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public Guid DocumentId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { 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 document = await _context.Documents + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + var permission = await _context.Permissions + .FirstOrDefaultAsync(x => x.DocumentId == request.DocumentId + && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + if ((permission is null || !permission.AllowedOperations.Contains(DocumentOperation.Read.ToString())) + && document.ImporterId != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("You do not have permission to view this document shared users."); + } + + var permissions = _context.Permissions + .Include(x => x.Employee) + .Where(x => x.DocumentId == request.DocumentId); + + if (request.SearchTerm is not null && !request.SearchTerm.Trim().Equals(string.Empty)) + { + permissions = permissions.Where(x => + x.Employee.Username.Trim().ToLower().Contains(request.SearchTerm.Trim().ToLower()) + || x.Employee.Email.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 ? 10 : request.Size; + + var list = await permissions + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var count = await permissions.CountAsync(cancellationToken); + + 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/Documents/Queries/GetDocumentById.cs b/src/Application/Documents/Queries/GetDocumentById.cs new file mode 100644 index 00000000..e1566cd4 --- /dev/null +++ b/src/Application/Documents/Queries/GetDocumentById.cs @@ -0,0 +1,89 @@ +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 Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetDocumentById +{ + 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; + private readonly IPermissionManager _permissionManager; + + 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.Folder) + .ThenInclude(x => x.Locker) + .ThenInclude(x => x.Room) + .Include(x => x.Department) + .Include(x => x.Importer) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (request.CurrentUser.Role.IsEmployee() && document.Status == DocumentStatus.Issued) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (ViolateConstraintForStaffAndEmployee(request.CurrentUser, document)) { + throw new UnauthorizedAccessException("You don't have permission to view this document."); + } + + return _mapper.Map(document); + } + + private bool ViolateConstraintForStaffAndEmployee(User user, Document document) + => !IsInSameDepartment(user, document) + && document.IsPrivate + && ( IsStaffAndNotInSameRoom(user, document) + || IsEmployeeAndDoesNotHasReadPermission(user, document) ); + + private bool IsStaffAndNotInSameRoom(User user, Document document) + { + var staff = _context.Staffs.FirstOrDefault(x => x.Id == user.Id); + return user.Role.IsStaff() + && staff is not null + && !staff.Room!.Id.Equals(document.Folder?.Locker.Room.Id); + } + + private bool IsEmployeeAndDoesNotHasReadPermission(User user, Document document) + => user.Role.IsEmployee() + && document.ImporterId != user.Id + && !_permissionManager.IsGranted(document.Id, DocumentOperation.Read, user.Id); + + private bool IsInSameDepartment(User user, Document document) + => user.Department == document.Department; + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentById/GetDocumentByIdQuery.cs b/src/Application/Documents/Queries/GetDocumentById/GetDocumentByIdQuery.cs deleted file mode 100644 index c1f62b70..00000000 --- a/src/Application/Documents/Queries/GetDocumentById/GetDocumentByIdQuery.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Documents.Queries.GetDocumentById; - -public record GetDocumentByIdQuery : IRequest -{ - public Guid Id { get; init; } -} - -public class GetDocumentByIdQueryHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public GetDocumentByIdQueryHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - public async Task Handle(GetDocumentByIdQuery 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.Id, cancellationToken); - - if (document is null) - { - throw new KeyNotFoundException("Document does not exist."); - } - - return _mapper.Map(document); - } -} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentTypes/GetAllDocumentTypesQuery.cs b/src/Application/Documents/Queries/GetDocumentTypes/GetAllDocumentTypesQuery.cs deleted file mode 100644 index 841aa23e..00000000 --- a/src/Application/Documents/Queries/GetDocumentTypes/GetAllDocumentTypesQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.ObjectModel; -using Application.Common.Interfaces; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Documents.Queries.GetDocumentTypes; - -public record GetAllDocumentTypesQuery : IRequest>; - -public class GetAllDocumentTypesQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - - public GetAllDocumentTypesQueryHandler(IApplicationDbContext context) - { - _context = context; - } - public async Task> Handle(GetAllDocumentTypesQuery request, CancellationToken cancellationToken) - { - return new ReadOnlyCollection(await _context.Documents.Select(x => x.DocumentType).Distinct() - .ToListAsync(cancellationToken)); - } -} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentTypes/GetDocumentTypesQuery.cs b/src/Application/Documents/Queries/GetDocumentTypes/GetDocumentTypesQuery.cs deleted file mode 100644 index b6f65030..00000000 --- a/src/Application/Documents/Queries/GetDocumentTypes/GetDocumentTypesQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.ObjectModel; -using Application.Common.Interfaces; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Documents.Queries.GetDocumentTypes; - -public record GetDocumentTypesQuery : IRequest>; - -public class GetDocumentTypesQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - - public GetDocumentTypesQueryHandler(IApplicationDbContext context) - { - _context = context; - } - public async Task> Handle(GetDocumentTypesQuery request, CancellationToken cancellationToken) - { - return new ReadOnlyCollection(await _context.Documents.Select(x => x.DocumentType) - .ToListAsync(cancellationToken)); - } -} \ 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/Entries/Commands/CreateEntry.cs b/src/Application/Entries/Commands/CreateEntry.cs new file mode 100644 index 00000000..0b6f4ae7 --- /dev/null +++ b/src/Application/Entries/Commands/CreateEntry.cs @@ -0,0 +1,175 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using Application.Helpers; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class CreateEntry { + public class Validator : AbstractValidator + { + private readonly string[] _allowedExtensions = + { + "pdf", + "doc", + "docx", + "xls", + "xlsx", + "ppt", + "pptx", + }; + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.FileExtension) + .Must((command, extension) => + { + if (!command.IsDirectory && !_allowedExtensions.Contains(extension)) + { + throw new ConflictException("This file is not supported."); + } + return true; + }); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Entry's name is required.") + .Matches("^[\\p{L}A-Za-z()_.\\s\\-0-9]*$").WithMessage("Invalid name format.") + .MaximumLength(256).WithMessage("Name cannot exceed 256 characters."); + + RuleFor(x => x.Path) + .NotEmpty().WithMessage("Entry's path is required.") + .Matches("^(/(?!/)[\\p{L}A-Za-z_.\\s\\-0-9]*)+(? + { + public User CurrentUser { get; init; } = null!; + public string Name { get; init; } = null!; + public string Path { get; init; } = null!; + public bool IsDirectory { get; init; } + public MemoryStream? FileData { get; init; } + public string? FileType { get; init; } + public string? FileExtension { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public Handler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var baseDirectoryExists = await _context.Entries.AnyAsync( + x => request.Path.Trim().ToLower() + .Equals((x.Path.Equals("/") ? (x.Path + x.Name) : (x.Path + "/" + x.Name)).ToLower()) + && x.FileId == null && x.OwnerId == request.CurrentUser.Id, cancellationToken); + + if (!request.Path.Equals("/") && !baseDirectoryExists) + { + throw new ConflictException("Base directory does not exist."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var entryEntity = new Entry() + { + Name = request.IsDirectory ? request.Name.Trim() : request.Name.Trim()[0..request.Name.Trim().LastIndexOf(".", StringComparison.Ordinal)], + Path = request.Path.Trim(), + CreatedBy = request.CurrentUser.Id, + Uploader = request.CurrentUser, + Created = localDateTimeNow, + Owner = request.CurrentUser, + OwnerId = request.CurrentUser.Id, + SizeInBytes = null + }; + + if (request.IsDirectory) + { + var entry = await _context.Entries.FirstOrDefaultAsync( + x => x.Name.Trim().Equals(request.Name.Trim()) + && x.Path.Trim().Equals(request.Path.Trim()) + && x.FileId == null + && x.OwnerId == request.CurrentUser.Id, cancellationToken); + + if (entry is not null) + { + throw new ConflictException("Directory already exists."); + } + } + else + { + var entries = _context.Entries.AsQueryable() + .Where(x => x.Name.Trim().Substring(0, entryEntity.Name.Trim().Length).Equals(entryEntity.Name.Trim()) + && x.Path.Trim().Equals(request.Path.Trim()) + && x.FileId != null + && x.OwnerId == request.CurrentUser.Id); + + if (request.FileData!.Length > FileUtil.ToByteFromMb(20)) + { + throw new ConflictException("File size must be lower than 20MB"); + } + + if (entries.Any()) + { + var i = 0; + while (true) + { + var temp = ""; + temp = i == 0 ? entryEntity.Name : $"{entryEntity.Name} ({i})"; + var checkEntry = await entries.AnyAsync(x => x.Name.Equals(temp),cancellationToken); + + if (!checkEntry) + { + entryEntity.Name = temp; + break; + } + + i++; + } + } + + var fileEntity = new FileEntity() + { + FileData = request.FileData.ToArray(), + FileType = request.FileType!, + FileExtension = request.FileExtension, + }; + entryEntity.FileId = fileEntity.Id; + entryEntity.File = fileEntity; + entryEntity.SizeInBytes = request.FileData!.Length; + + await _context.Files.AddAsync(fileEntity, cancellationToken); + } + + var result = await _context.Entries.AddAsync(entryEntity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), entryEntity.Id, request.CurrentUser.Id)) + { + _logger.LogCreateEntry(request.CurrentUser.Username, entryEntity.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Commands/CreateSharedEntry.cs b/src/Application/Entries/Commands/CreateSharedEntry.cs new file mode 100644 index 00000000..6cfcb6af --- /dev/null +++ b/src/Application/Entries/Commands/CreateSharedEntry.cs @@ -0,0 +1,169 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class CreateSharedEntry +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Entry's name is required.") + .Matches("^[\\p{L}A-Za-z_.\\s\\-0-9]*$").WithMessage("Invalid name format.") + .MaximumLength(256).WithMessage("Name cannot exceed 256 characters."); + } + } + public record Command : IRequest + { + public Guid EntryId { get; init; } + public User CurrentUser { get; init; } = null!; + public string Name { get; init; } = null!; + public bool IsDirectory { get; init; } + public MemoryStream? FileData { get; init; } + public string? FileType { get; init; } + public string? FileExtension { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var permission = await _context.EntryPermissions + .Include(x => x.Entry) + .ThenInclude(y => y.Uploader) + .Include(x => x.Entry) + .ThenInclude(x => x.Owner) + .FirstOrDefaultAsync(x => x.EmployeeId == request.CurrentUser.Id && + x.EntryId == request.EntryId, cancellationToken); + if (permission is null) + { + throw new UnauthorizedAccessException("User is not allowed to create an entry."); + } + + if (!permission.Entry.IsDirectory) + { + throw new ConflictException("This is a file."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var entryPath = permission.Entry.Path.Equals("/") ? permission.Entry.Path + permission.Entry.Name : $"{permission.Entry.Path}/{permission.Entry.Name}"; + + var entity = new Entry() + { + Name = request.IsDirectory ? request.Name.Trim() : request.Name.Trim()[0..request.Name.Trim().LastIndexOf(".", StringComparison.Ordinal)], + Path = entryPath, + CreatedBy = request.CurrentUser.Id, + Uploader = request.CurrentUser, + Created = localDateTimeNow, + Owner = permission.Entry.Owner, + OwnerId = permission.Entry.Owner.Id, + }; + + if (request.IsDirectory) + { + var entry = await _context.Entries + .FirstOrDefaultAsync( + x => x.Name.Trim().Equals(request.Name.Trim()) + && x.Path.Equals(entryPath) + && x.FileId == null,cancellationToken); + + if (entry is not null) + { + throw new ConflictException("Directory already exists."); + } + } + else + { + if (request.FileData!.Length > 20971520) + { + throw new ConflictException("File size must be lower than 20MB"); + } + + var entries = _context.Entries + .Where(x => x.Name.Trim().Substring(0, entity.Name.Trim().Length).Equals(entity.Name.Trim()) + && x.Path.Trim().Equals(entryPath) + && x.FileId != null + && x.OwnerId == permission.Entry.OwnerId); + + if (entries.Any()) + { + var i = 0; + while (true) + { + var temp = ""; + temp = i == 0 ? entity.Name : $"{entity.Name} ({i})"; + var checkEntry = await entries.AnyAsync(x => x.Name.Equals(temp),cancellationToken); + + if (!checkEntry) + { + entity.Name = temp; + break; + } + + i++; + } + } + + var fileEntity = new FileEntity() + { + FileData = request.FileData.ToArray(), + FileType = request.FileType!, + FileExtension = request.FileExtension, + }; + + await _context.Files.AddAsync(fileEntity, cancellationToken); + + entity.FileId = fileEntity.Id; + entity.File = fileEntity; + } + + var result = await _context.Entries.AddAsync(entity, cancellationToken); + + var permissionEntity = new EntryPermission() + { + EntryId = result.Entity.Id, + Entry = result.Entity, + EmployeeId = request.CurrentUser.Id, + ExpiryDateTime = null, + AllowedOperations = $"{EntryOperation.View.ToString()},{EntryOperation.Edit.ToString()}" + }; + + await _context.EntryPermissions.AddAsync(permissionEntity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), entity.Id, request.CurrentUser.Id)) + { + _logger.LogCreateSharedEntry(request.CurrentUser.Username, entity.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Commands/DeleteBinEntry.cs b/src/Application/Entries/Commands/DeleteBinEntry.cs new file mode 100644 index 00000000..0b4ff8cb --- /dev/null +++ b/src/Application/Entries/Commands/DeleteBinEntry.cs @@ -0,0 +1,88 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Entries.Commands; + +public class DeleteBinEntry +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private const string BinString = "_bin"; + private readonly ILogger _logger; + + public Handler(IMapper mapper, IApplicationDbContext context, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _mapper = mapper; + _context = context; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .Include(x=> x.Owner) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + var entryPath = entry.Path; + var firstSlashIndex = entryPath.IndexOf("/", StringComparison.Ordinal); + var binCheck = firstSlashIndex < 0 ? entryPath : entryPath.Substring(0, firstSlashIndex); + + var path1 = request.CurrentUser.Username + BinString; + if (!binCheck.Equals(path1)) + { + throw new NotChangedException("Entry is not in bin."); + } + + if (!entry.Owner.Username.Equals(request.CurrentUser.Username)) + { + throw new UnauthorizedAccessException("You do not have the permission to delete this entry."); + } + + if (entry.IsDirectory) + { + var path = entry.Path[^1].Equals('/') ? entry.Path + entry.Name : $"{entry.Path}/{entry.Name}"; + var pattern = $"{path}/%"; + var childEntries = _context.Entries.Where(x => + x.Path.Trim().Equals(path) + || EF.Functions.Like(x.Path, pattern)); + + foreach (var childEntry in childEntries) + { + _context.Entries.Remove(childEntry); + } + } + + var result = _context.Entries.Remove(entry); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), entry.Id, request.CurrentUser.Id)) + { + _logger.LogDeleteBinEntry(request.CurrentUser.Username, entry.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} diff --git a/src/Application/Entries/Commands/DownloadDigitalFile.cs b/src/Application/Entries/Commands/DownloadDigitalFile.cs new file mode 100644 index 00000000..88ecfe3f --- /dev/null +++ b/src/Application/Entries/Commands/DownloadDigitalFile.cs @@ -0,0 +1,88 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Operations; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Entries.Commands; + +public class DownloadDigitalFile +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Result + { + public MemoryStream Content { get; set; } = null!; + public string FileType { get; set; } = null!; + public string FileName { get; set; } = null!; + } + + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .Include(x => x.File) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + if (entry.IsDirectory) + { + throw new ConflictException("Can not download folder."); + } + + if (entry.OwnerId != request.CurrentUser.Id) + { + var permission = await _context.EntryPermissions.FirstOrDefaultAsync( + x => x.EntryId == request.EntryId + && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + + if (permission is null || + !permission.AllowedOperations + .Split(",") + .Contains(EntryOperation.View.ToString())) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + + var content = new MemoryStream(entry.File!.FileData); + var fileType = entry.File!.FileType; + var fileId = entry.File!.Id; + + using (Logging.PushProperties(nameof(entry), entry.Id, request.CurrentUser.Id)) + { + _logger.LogDownLoadFile(request.CurrentUser.Username, fileId.ToString()); + } + + return new Result() + { + Content = content, + FileType = fileType, + FileName = $"{entry.Name}.{entry.File.FileExtension}", + }; + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Commands/MoveEntryToBin.cs b/src/Application/Entries/Commands/MoveEntryToBin.cs new file mode 100644 index 00000000..4ec05da9 --- /dev/null +++ b/src/Application/Entries/Commands/MoveEntryToBin.cs @@ -0,0 +1,105 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class MoveEntryToBin +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private const string BinString = "_bin"; + private readonly ILogger _logger; + + public Handler(IMapper mapper, IApplicationDbContext context, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _mapper = mapper; + _context = context; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .Include(x=> x.Owner) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + if (!entry.Owner.Id.Equals(request.CurrentUser.Id)) + { + throw new UnauthorizedAccessException("You do not have permission to move this entry into bin."); + } + + var ownerUsername = entry.Owner.Username; + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + if (entry.IsDirectory) + { + var path = entry.Path.Equals("/") ? entry.Path + entry.Name : $"{entry.Path}/{entry.Name}"; + var pattern = $"{path}/%"; + var childEntries = _context.Entries.Where(x => + x.Path.Trim().Equals(path) + || EF.Functions.Like(x.Path, pattern)); + + var ids = childEntries.Select(x => x.Id); + var permissions = _context.EntryPermissions.Where(x => ids.Contains(x.EntryId)); + _context.EntryPermissions.RemoveRange(permissions); + + var c = entry.Path.Length; + foreach (var childEntry in childEntries) + { + var childBinPath = ownerUsername + BinString; + if (!childEntry.Path.Equals("/")) + { + childBinPath += $"/{childEntry.Path[c..]}"; + } + childEntry.OldPath = childEntry.Path; + childEntry.Path = childBinPath; + childEntry.LastModified = localDateTimeNow; + childEntry.LastModifiedBy = request.CurrentUser.Id; + _context.Entries.Update(childEntry); + } + } + + var binPath = $"{ownerUsername}{BinString}"; + entry.OldPath = entry.Path; + entry.Path = binPath; + entry.LastModified = localDateTimeNow; + entry.LastModifiedBy = request.CurrentUser.Id; + + var entryPermissions = _context.EntryPermissions.Where(x => x.EntryId == entry.Id); + _context.EntryPermissions.RemoveRange(entryPermissions); + + var result = _context.Entries.Update(entry); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(entry), entry.Id, request.CurrentUser.Id)) + { + _logger.LogMoveEntryToBin(request.CurrentUser.Username, entry.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} diff --git a/src/Application/Entries/Commands/RestoreBinEntry.cs b/src/Application/Entries/Commands/RestoreBinEntry.cs new file mode 100644 index 00000000..413ebab5 --- /dev/null +++ b/src/Application/Entries/Commands/RestoreBinEntry.cs @@ -0,0 +1,148 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class RestoreBinEntry +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private const string BinString = "_bin"; + private readonly ILogger _logger; + public Handler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var binEntry = await _context.Entries + .Include(x => x.Owner) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (binEntry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + var entryPath = binEntry.Path; + var firstSlashIndex = entryPath.IndexOf("/", StringComparison.Ordinal); + var binCheck = firstSlashIndex < 0 ? entryPath : entryPath.Substring(0, firstSlashIndex); + + var path1 = request.CurrentUser.Username + BinString; + if (!binCheck.Equals(path1)) + { + throw new NotChangedException("Entry is not in bin."); + } + + if (binEntry.Owner.Id != request.CurrentUser.Id) + { + throw new UnauthorizedAccessException("You do not have the permission to restore this entry."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var entryCheckPath = entryPath.Replace(binCheck, ""); + + var entryCheck = await _context.Entries.FirstOrDefaultAsync(x => + (x.Path.Equals("/") ? x.Path + x.Name : x.Path + "/" + x.Name).Equals(entryCheckPath + "/" + binEntry.Name) + && x.OwnerId == request.CurrentUser.Id + && ((x.FileId != null && binEntry.FileId != null) || (x.FileId == null && binEntry.FileId == null)), cancellationToken); + + if (entryCheck is not null) + { + throw new ConflictException("Entry already exists outside of bin."); + } + + var splitPath = binEntry.OldPath!.Split("/"); + var currentPath = "/"; + foreach (var node in splitPath) + { + if (Array.IndexOf(splitPath, node) == 0) + { + continue; + } + + if (currentPath.Equals("/") && node.Equals("")) + { + continue; + } + + var entrySearch = await _context.Entries.FirstOrDefaultAsync(x => x.Path.Equals(currentPath) + && x.Name.Equals(node), cancellationToken); + + if (entrySearch is not null) + { + currentPath = currentPath.Equals("/") ? currentPath + node : currentPath + "/" + node; + continue; + } + + var parentEntry = new Entry() + { + Name = node, + Path = currentPath, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + OwnerId = request.CurrentUser.Id, + Owner = request.CurrentUser, + Uploader = request.CurrentUser, + }; + await _context.Entries.AddAsync(parentEntry, cancellationToken); + currentPath = currentPath.Equals("/") ? currentPath + node : currentPath + "/" + node; + } + + if (binEntry.IsDirectory) + { + var path = binEntry.Path[^1].Equals('/') ? binEntry.Path + binEntry.Name : $"{binEntry.Path}/{binEntry.Name}"; + var pattern = $"{path}/%"; + var childEntries = _context.Entries.Where(x => + x.Path.Trim().Equals(path) + || EF.Functions.Like(x.Path, pattern)); + + foreach (var childEntry in childEntries) + { + childEntry.Path = childEntry.Path.Replace(binCheck, ""); + childEntry.OldPath = null; + childEntry.LastModified = localDateTimeNow; + childEntry.LastModifiedBy = request.CurrentUser.Id; + _context.Entries.Update(childEntry); + } + } + + binEntry.Path = binEntry.OldPath; + binEntry.OldPath = null; + binEntry.LastModified = localDateTimeNow; + binEntry.LastModifiedBy = request.CurrentUser.Id; + + var result = _context.Entries.Update(binEntry); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), binEntry.Id, request.CurrentUser.Id)) + { + _logger.LogRestoreBinEntry(request.CurrentUser.Username, binEntry.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} diff --git a/src/Application/Entries/Commands/ShareEntry.cs b/src/Application/Entries/Commands/ShareEntry.cs new file mode 100644 index 00000000..6d6deb73 --- /dev/null +++ b/src/Application/Entries/Commands/ShareEntry.cs @@ -0,0 +1,188 @@ +using System.Configuration; +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using Domain.Events; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class ShareEntry +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + public Guid UserId { get; init; } + public DateTime? ExpiryDate { get; init; } + public bool CanView { get; init; } + public bool CanEdit { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext applicationDbContext, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = applicationDbContext; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + var canChangeEntryPermission = _context.EntryPermissions.Any(x => + x.EntryId == request.EntryId + && x.EmployeeId == request.CurrentUser.Id + && x.AllowedOperations.Contains(EntryOperation.Edit.ToString())); + + if (entry.OwnerId != request.CurrentUser.Id && !canChangeEntryPermission) + { + throw new UnauthorizedAccessException("You are not allow to change permission of this entry."); + } + + 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.Id == entry.OwnerId) + { + throw new ConflictException("You can not modify owner's permission."); + } + + if (request.ExpiryDate < _dateTimeProvider.DateTimeNow) + { + throw new ConflictException("Expiry date cannot be in the past."); + } + + var allowOperations = GenerateAllowOperations(request); + + + var isShareRoot = !await _context.EntryPermissions + .AnyAsync(x => (x.Entry.Path == "/" ? x.Entry.Path + x.Entry.Name : x.Entry.Path + "/" + x.Entry.Name) == entry.Path + && x.EmployeeId == request.UserId + && x.Entry.FileId == null, cancellationToken); + + await GrantOrRevokePermission(entry, user, allowOperations, request.ExpiryDate, isShareRoot, cancellationToken); + + if (entry.IsDirectory) + { + // Update permissions for child Entries + var path = entry.Path.Equals("/") ? entry.Path + entry.Name : $"{entry.Path}/{entry.Name}"; + var pattern = $"{path}/%"; + var childEntries = _context.Entries + .Where(x => (x.Path.Equals(path) || EF.Functions.Like(x.Path, pattern)) + && x.OwnerId == entry.OwnerId) + .ToList(); + foreach (var childEntry in childEntries) + { + var childAllowOperations = GenerateAllowOperations(request); + await GrantOrRevokePermission(childEntry, user, childAllowOperations, request.ExpiryDate, false, cancellationToken); + } + } + + if (request.CanView) + { + var p = "view"; + if (request.CanEdit) p = "edit"; + entry.AddDomainEvent(new ShareEntryEvent(entry.Name, + request.CurrentUser.FirstName + request.CurrentUser.LastName, + entry.Owner.FirstName + entry.Owner.LastName, user.Email, entry.IsDirectory, p, entry.Id.ToString())); + } + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), entry.Id, request.CurrentUser.Id)) + { + _logger.LogShareEntry(request.CurrentUser.Username, entry.Id.ToString(), user.Username); + } + var result = await _context.EntryPermissions.FirstOrDefaultAsync(x => + x.EntryId == request.EntryId + && x.EmployeeId == request.UserId, cancellationToken); + return _mapper.Map(result); + } + + private async Task GrantOrRevokePermission( + Entry entry, + User user, + string allowOperations, + DateTime? expiryDate, + bool isSharedRoot, + CancellationToken cancellationToken) + { + var existedPermission = await _context.EntryPermissions.FirstOrDefaultAsync(x => + x.EntryId == entry.Id + && x.EmployeeId == user.Id, cancellationToken); + + if (existedPermission is null) + { + if (string.IsNullOrEmpty(allowOperations)) return; + var entryPermission = new EntryPermission + { + EmployeeId = user.Id, + EntryId = entry.Id, + AllowedOperations = allowOperations, + ExpiryDateTime = expiryDate is null ? null : LocalDateTime.FromDateTime(expiryDate.Value), + IsSharedRoot = isSharedRoot, + Employee = user, + Entry = entry, + }; + await _context.EntryPermissions.AddAsync(entryPermission, cancellationToken); + } + else if (string.IsNullOrEmpty(allowOperations)) + { + _context.EntryPermissions.Remove(existedPermission); + } + else + { + existedPermission.AllowedOperations = allowOperations; + existedPermission.ExpiryDateTime = expiryDate is null ? null : LocalDateTime.FromDateTime(expiryDate.Value); + _context.EntryPermissions.Update(existedPermission); + } + } + + private static string GenerateAllowOperations(Command request) + { + var allowOperations = new CommaDelimitedStringCollection(); + + if (request.CanView) + { + allowOperations.Add(EntryOperation.View.ToString()); + } + else + { + return string.Empty; + } + + if (request is { CanView: true, CanEdit: true }) + { + allowOperations.Add(EntryOperation.Edit.ToString()); + } + + return allowOperations.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Commands/UpdateEntry.cs b/src/Application/Entries/Commands/UpdateEntry.cs new file mode 100644 index 00000000..f4571d80 --- /dev/null +++ b/src/Application/Entries/Commands/UpdateEntry.cs @@ -0,0 +1,135 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Entries.Commands; + +public class UpdateEntry +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Entry's name is required.") + .Matches("^[\\p{L}A-Za-z_.\\s\\-0-9]*$").WithMessage("Invalid name format.") + .MaximumLength(256).WithMessage("Name cannot exceed 256 characters."); + } + } + + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + public string Name { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .Include(x => x.Owner) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + if (entry.OwnerId != request.CurrentUser.Id) + { + var permission = await _context.EntryPermissions.FirstOrDefaultAsync( + x => x.EntryId == request.EntryId + && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + + if (permission is null || + !permission.AllowedOperations + .Split(",") + .Contains(EntryOperation.Edit.ToString())) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + + var nameExisted = await _context.Entries + .AnyAsync(x => x.Id != entry.Id + && x.Path.Equals(entry.Path) + && x.Name.Equals(request.Name), cancellationToken); + + if (nameExisted) + { + throw new ConflictException("Name has already exist."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var oldPath = entry.Path.Equals("/") ? entry.Path + entry.Name : $"{entry.Path}/{entry.Name}"; + + entry.Name = request.Name; + entry.LastModified = localDateTimeNow; + entry.LastModifiedBy = request.CurrentUser.Id; + + var result = _context.Entries.Update(entry); + + if (entry.IsDirectory) + { + // Update permissions for child Entries + var newPath = entry.Path.Equals("/") ? entry.Path + entry.Name : $"{entry.Path}/{entry.Name}"; + var pattern = $"{oldPath}/%"; + var childEntries = _context.Entries + .Where(x => (x.Path.Equals(oldPath) || EF.Functions.Like(x.Path, pattern)) + && x.OwnerId == entry.OwnerId) + .ToList(); + var count = oldPath.Length; + foreach (var childEntry in childEntries) + { + if (childEntry.Path.Equals(oldPath)) + { + childEntry.Path = newPath; + } + else + { + var after = childEntry.Path[(count+1)..]; + childEntry.Path = $"{newPath}/{after}"; + } + } + + _context.Entries.UpdateRange(childEntries); + } + + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Entry), entry.Id, request.CurrentUser.Id)) + { + _logger.LogUpdateEntry(request.CurrentUser.Username, entry.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/EventHandlers/ShareEntryEventHandler.cs b/src/Application/Entries/EventHandlers/ShareEntryEventHandler.cs new file mode 100644 index 00000000..eeb2a243 --- /dev/null +++ b/src/Application/Entries/EventHandlers/ShareEntryEventHandler.cs @@ -0,0 +1,21 @@ +using Application.Common.Interfaces; +using Domain.Events; +using MediatR; + +namespace Application.Entries.EventHandlers; + +public class ShareEntryEventHandler : INotificationHandler +{ + private readonly IMailService _mailService; + + public ShareEntryEventHandler(IMailService mailService) + { + _mailService = mailService; + } + + public async Task Handle(ShareEntryEvent notification, CancellationToken cancellationToken) + { + _mailService.SendShareEntryHtmlMail(notification.IsDirectory, notification.EntryName, notification.SharerName, + notification.Operation, notification.OwnerName, notification.SharedUserEmail, notification.Path); + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/DownloadSharedEntry.cs b/src/Application/Entries/Queries/DownloadSharedEntry.cs new file mode 100644 index 00000000..eac78b6f --- /dev/null +++ b/src/Application/Entries/Queries/DownloadSharedEntry.cs @@ -0,0 +1,71 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Operations; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class DownloadSharedEntry +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Result + { + public string FileName { get; set; } = null!; + public string FileType { get; set; } = null!; + public byte[] FileData { get; set; } = null!; + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + public QueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var entry = await _context.Entries.FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + if (entry.IsDirectory) + { + throw new ConflictException("Entry cannot be downloaded."); + } + + if (entry.OwnerId != request.CurrentUser.Id) + { + var permission = await _context.EntryPermissions.FirstOrDefaultAsync( + x => x.EntryId == request.EntryId && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + + if (permission is null || + !permission.AllowedOperations + .Split(",") + .Contains(EntryOperation.View.ToString())) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + + var result = new Result + { + FileName = $"{entry.Name}.{entry.File!.FileExtension}", + FileType = entry.File.FileType, + FileData = entry.File.FileData, + }; + + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/GetAllBinEntriesPaginated.cs b/src/Application/Entries/Queries/GetAllBinEntriesPaginated.cs new file mode 100644 index 00000000..8e5742b9 --- /dev/null +++ b/src/Application/Entries/Queries/GetAllBinEntriesPaginated.cs @@ -0,0 +1,86 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetAllBinEntriesPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public string EntryPath { get; init; } = null!; + 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 path = $"{request.CurrentUser.Username}_bin"; + var count1 = path.Length; + + if (!request.EntryPath.Equals("/")) + { + path += request.EntryPath; + } + + var entries = _context.Entries + .Include(x => x.Owner) + .Include(x => x.File) + .Where(x => x.Owner.Id == request.CurrentUser.Id + && x.Path == path); + + + // var realPath = $"{request.CurrentUser.Username}_bin{request.EntryPath}"; + + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + entries = entries.Where(x => + x.Name.Contains(request.SearchTerm.Trim())); + } + + var sortBy = request.SortBy; + + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(EntryDto.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 ? 10 : request.Size; + + var count = await entries.CountAsync(cancellationToken); + var list = await entries + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + list.ForEach(x => { x.Path = x.Path.Length == count1 ? "/" : x.Path[count1..]; }); + + 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/Entries/Queries/GetAllEntriesPaginated.cs b/src/Application/Entries/Queries/GetAllEntriesPaginated.cs new file mode 100644 index 00000000..6eb64ff8 --- /dev/null +++ b/src/Application/Entries/Queries/GetAllEntriesPaginated.cs @@ -0,0 +1,75 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetAllEntriesPaginated +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.EntryPath) + .NotEmpty().WithMessage("File's path is required.") + .Matches("^(/(?!/)[\\p{L}A-Za-z_.\\s\\-0-9]*)+(?> + { + public User CurrentUser { get; init; } = null!; + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + public string EntryPath { get; init; } = null!; + } + + 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 entries = _context.Entries + .Include(x => x.File) + .Where(x => x.Path.Equals(request.EntryPath) && + x.OwnerId == request.CurrentUser.Id); + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(EntryDto.Path); + } + + 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 entries.CountAsync(cancellationToken); + var list = await entries + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + 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/Entries/Queries/GetAllSharedEntriesPaginated.cs b/src/Application/Entries/Queries/GetAllSharedEntriesPaginated.cs new file mode 100644 index 00000000..25f80f1e --- /dev/null +++ b/src/Application/Entries/Queries/GetAllSharedEntriesPaginated.cs @@ -0,0 +1,89 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetAllSharedEntriesPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + public Guid? EntryId { 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 permissions = _context.EntryPermissions + .Include(x => x.Entry) + .ThenInclude(y => y.Uploader) + .Include(x => x.Entry) + .ThenInclude(y => y.Owner) + .Include(x => x.Entry) + .ThenInclude(y => y.File) + .Where(x => + x.EmployeeId == request.CurrentUser.Id + && x.AllowedOperations + .Contains(EntryOperation.View.ToString())); + + IQueryable entries; + if (request.EntryId is null) + { + entries = permissions + .Where(x => x.IsSharedRoot) + .Select(x => x.Entry); + } + else + { + var baseEntry = await permissions + .Select(x => x.Entry) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (baseEntry is null) + { + throw new KeyNotFoundException("Base entry does not exist."); + } + + var basePath = baseEntry.Path.Equals("/") + ? $"{baseEntry.Path}{baseEntry.Name}" + : $"{baseEntry.Path}/{baseEntry.Name}"; + var pattern = $"{basePath}/"; + + entries = permissions + .Select(x => x.Entry) + .Where(x => x.Path.Equals(basePath) + || EF.Functions.Like(x.Path, pattern)); + } + return await entries + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/GetBinEntryById.cs b/src/Application/Entries/Queries/GetBinEntryById.cs new file mode 100644 index 00000000..44213b79 --- /dev/null +++ b/src/Application/Entries/Queries/GetBinEntryById.cs @@ -0,0 +1,61 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetBinEntryById +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public Handler(IMapper mapper, IApplicationDbContext context) + { + _mapper = mapper; + _context = context; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .Include(x => x.Owner) + .Include(x => x.Uploader) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + var entryPath = entry.Path; + var firstSlashIndex = entryPath.IndexOf("/", StringComparison.Ordinal); + var binCheck = entryPath.Substring(0, firstSlashIndex); + + if (!binCheck.Contains("_bin")) + { + throw new ConflictException("Entry is not in bin."); + } + + if (!entry.Owner.Username.Equals(request.CurrentUser.Username)) + { + throw new UnauthorizedAccessException("You do not have permission to view this entry"); + } + + entry.Path = entry.Path[binCheck.Length..]; + + return _mapper.Map(entry); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/GetEntryById.cs b/src/Application/Entries/Queries/GetEntryById.cs new file mode 100644 index 00000000..0a64c880 --- /dev/null +++ b/src/Application/Entries/Queries/GetEntryById.cs @@ -0,0 +1,45 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetEntryById +{ + public record Query : IRequest + { + public Guid EntryId { 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 entry = await _context.Entries + .Include(x => x.File) + .Include(x => x.Uploader) + .ThenInclude(x => x.Department) + .Include(x => x.Owner) + .ThenInclude(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + return _mapper.Map(entry); + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/GetPermissions.cs b/src/Application/Entries/Queries/GetPermissions.cs new file mode 100644 index 00000000..17aaa82e --- /dev/null +++ b/src/Application/Entries/Queries/GetPermissions.cs @@ -0,0 +1,77 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Digital; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetPermissions +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { 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 entry = await _context.Entries + .Include(x => x.File) + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new ConflictException("Entry does not exist."); + } + + if (entry.OwnerId == request.CurrentUser.Id) + { + return CreateEntryPermissionDto(entry.Id, entry.OwnerId, + true, true, false); + } + + var permission = await _context.EntryPermissions + .FirstOrDefaultAsync(x => + x.EntryId == entry.Id + && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + + if (permission is null) + { + return CreateEntryPermissionDto(entry.Id, request.CurrentUser.Id, + false, false, false); + } + + return _mapper.Map(permission); + } + + private static EntryPermissionDto CreateEntryPermissionDto( + Guid entryId, Guid userId, + bool canView, bool canEdit, + bool isSharedRoot) + { + return new EntryPermissionDto + { + EmployeeId = userId, + EntryId = entryId, + CanView = canView, + CanEdit = canEdit, + IsSharedRoot = isSharedRoot, + }; + } + } +} \ No newline at end of file diff --git a/src/Application/Entries/Queries/GetSharedEntryById.cs b/src/Application/Entries/Queries/GetSharedEntryById.cs new file mode 100644 index 00000000..e515843a --- /dev/null +++ b/src/Application/Entries/Queries/GetSharedEntryById.cs @@ -0,0 +1,56 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Entries.Queries; + +public class GetSharedEntryById +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + } + + public class Handler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public Handler(IMapper mapper, IApplicationDbContext context) + { + _mapper = mapper; + _context = context; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var permission = await _context.EntryPermissions + .Include(x => x.Entry) + .ThenInclude(x => x.Uploader) + .Include(x => x.Entry) + .ThenInclude(x => x.Owner) + .Include(x => x.Entry) + .ThenInclude(x => x.File) + .FirstOrDefaultAsync(x => x.EntryId == request.EntryId && x.EmployeeId == request.CurrentUser.Id, + cancellationToken); + + if (permission is null) + { + throw new KeyNotFoundException("Shared entry does not exist."); + } + + if (!permission.AllowedOperations.Contains(EntryOperation.View.ToString())) + { + throw new NotAllowedException("You do not have permission to view this shared entry."); + } + + return _mapper.Map(permission.Entry); + } + } +} \ No newline at end of file diff --git a/src/Application/Folders/Commands/AddFolder.cs b/src/Application/Folders/Commands/AddFolder.cs new file mode 100644 index 00000000..59bc069c --- /dev/null +++ b/src/Application/Folders/Commands/AddFolder.cs @@ -0,0 +1,138 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Exceptions; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Folders.Commands; + +public class AddFolder +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(f => f.Name) + .NotEmpty().WithMessage("Name is required.") + .MaximumLength(64).WithMessage("Name cannot exceed 64 characters."); + + RuleFor(f => f.Description) + .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); + + RuleFor(f => f.Capacity) + .NotEmpty().WithMessage("Folder capacity is required.") + .GreaterThanOrEqualTo(1).WithMessage("Folder's capacity cannot be less than 1."); + + RuleFor(f => f.LockerId) + .NotEmpty().WithMessage("LockerId is required."); + } + } + + 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; } + public Guid LockerId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var locker = await _context.Lockers + .Include(x => x.Room) + .ThenInclude(y => y.Department) + .FirstOrDefaultAsync(l => l.Id == request.LockerId, cancellationToken); + + if (locker is null) + { + throw new KeyNotFoundException("Locker does not exist."); + } + + if (locker.NumberOfFolders >= locker.Capacity) + { + throw new LimitExceededException("This locker cannot accept more folders."); + } + + if (request.CurrentUser.Role.IsStaff() + && (locker.Room.Id != request.CurrentStaffRoomId + || !LockerIsInRoom(locker, request.CurrentStaffRoomId))) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + 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(), + Description = request.Description, + NumberOfDocuments = 0, + Capacity = request.Capacity, + Locker = locker, + IsAvailable = true, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var result = await _context.Folders.AddAsync(entity, cancellationToken); + locker.NumberOfFolders += 1; + _context.Lockers.Update(locker); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Folder), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogAddFolder(result.Entity.Id.ToString(), + result.Entity.Locker.Id.ToString(), + locker.Room.Id.ToString(), + locker.Room.Department.Name); + } + + 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/AddFolder/AddFolderCommand.cs b/src/Application/Folders/Commands/AddFolder/AddFolderCommand.cs deleted file mode 100644 index e3fd551d..00000000 --- a/src/Application/Folders/Commands/AddFolder/AddFolderCommand.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Entities.Physical; -using Domain.Exceptions; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Folders.Commands.AddFolder; - -public record AddFolderCommand : IRequest -{ - public string Name { get; init; } - public string? Description { get; init; } - public int Capacity { get; init; } - public Guid LockerId { get; init; } -} - -public class AddFolderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public AddFolderCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(AddFolderCommand request, CancellationToken cancellationToken) - { - var locker = await _context.Lockers.FirstOrDefaultAsync(l => l.Id == request.LockerId, cancellationToken); - - if (locker is null) - { - throw new KeyNotFoundException("Locker does not exist."); - } - - if (locker.NumberOfFolders >= locker.Capacity) - { - throw new LimitExceededException("This locker cannot accept more folders."); - } - - var folder = await _context.Folders.FirstOrDefaultAsync(x => x.Name.Trim().Equals(request.Name.Trim()) && x.Locker.Id.Equals(request.LockerId), cancellationToken); - - if (folder is not null) - { - throw new ConflictException("Folder's name already exists."); - } - - var entity = new Folder - { - Name = request.Name.Trim(), - Description = request.Description, - NumberOfDocuments = 0, - Capacity = request.Capacity, - Locker = locker, - IsAvailable = true - }; - var result = await _context.Folders.AddAsync(entity, cancellationToken); - locker.NumberOfFolders += 1; - _context.Lockers.Update(locker); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} diff --git a/src/Application/Folders/Commands/AddFolder/AddFolderCommandValidator.cs b/src/Application/Folders/Commands/AddFolder/AddFolderCommandValidator.cs deleted file mode 100644 index f7717f54..00000000 --- a/src/Application/Folders/Commands/AddFolder/AddFolderCommandValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FluentValidation; - -namespace Application.Folders.Commands.AddFolder; - -public class AddFolderCommandValidator : AbstractValidator -{ - public AddFolderCommandValidator() - { - - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(f => f.Name) - .NotEmpty().WithMessage("Name is required.") - .MaximumLength(64).WithMessage("Name cannot exceed 64 characters."); - - RuleFor(f => f.Description) - .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); - - RuleFor(f => f.Capacity) - .NotEmpty().WithMessage("Folder capacity is required.") - .GreaterThanOrEqualTo(1).WithMessage("Folder's capacity cannot be less than 1."); - - RuleFor(f => f.LockerId) - .NotEmpty().WithMessage("LockerId is required."); - } - -} \ No newline at end of file diff --git a/src/Application/Folders/Commands/DisableFolder/DisableFolderCommand.cs b/src/Application/Folders/Commands/DisableFolder/DisableFolderCommand.cs deleted file mode 100644 index d49909a6..00000000 --- a/src/Application/Folders/Commands/DisableFolder/DisableFolderCommand.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Folders.Commands.DisableFolder; - -public record DisableFolderCommand : IRequest -{ - public Guid FolderId { get; init; } -} - -public class DisableFolderCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public DisableFolderCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(DisableFolderCommand request, CancellationToken cancellationToken) - { - var folder = await _context.Folders - .FirstOrDefaultAsync(f => f.Id.Equals(request.FolderId), cancellationToken); - - if (folder is null) - { - throw new KeyNotFoundException("Folder does not exist."); - } - - if (!folder.IsAvailable) - { - throw new InvalidOperationException("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/DisableFolder/DisableFolderCommandValidator.cs b/src/Application/Folders/Commands/DisableFolder/DisableFolderCommandValidator.cs deleted file mode 100644 index 04621166..00000000 --- a/src/Application/Folders/Commands/DisableFolder/DisableFolderCommandValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; - -namespace Application.Folders.Commands.DisableFolder; - -public class DisableFolderCommandValidator : AbstractValidator -{ - public DisableFolderCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(f => f.FolderId) - .NotEmpty().WithMessage("FolderId is required."); - } -} \ No newline at end of file diff --git a/src/Application/Folders/Commands/RemoveFolder.cs b/src/Application/Folders/Commands/RemoveFolder.cs new file mode 100644 index 00000000..557fe4c4 --- /dev/null +++ b/src/Application/Folders/Commands/RemoveFolder.cs @@ -0,0 +1,89 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Folders.Commands; + +public class RemoveFolder +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } + public Guid FolderId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 (request.CurrentUser.Role.IsStaff() + && (request.CurrentStaffRoomId is null || !FolderIsInRoom(folder, request.CurrentStaffRoomId.Value))) + { + throw new UnauthorizedAccessException("User cannot remove this resource."); + } + + 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 result = _context.Folders.Remove(folder); + locker.NumberOfFolders -= 1; + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Folder), folder.Id, request.CurrentUser.Id)) + { + _logger.LogRemoveFolder(folder.Id.ToString(), + folder.Locker.Id.ToString(), + folder.Locker.Room.Id.ToString(), + folder.Locker.Room.Department.Name); + } + + 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 new file mode 100644 index 00000000..8338cebb --- /dev/null +++ b/src/Application/Folders/Commands/UpdateFolder.cs @@ -0,0 +1,133 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Folders.Commands; + +public class UpdateFolder +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(f => f.Name) + .NotEmpty().WithMessage("Name is required.") + .MaximumLength(64).WithMessage("Name cannot exceed 64 characters."); + + RuleFor(f => f.Description) + .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); + + RuleFor(f => f.Capacity) + .NotEmpty().WithMessage("Folder capacity is required.") + .GreaterThanOrEqualTo(1).WithMessage("Folder's capacity cannot be less than 1."); + } + } + + 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; } + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 (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."); + } + + if (request.Capacity < folder.NumberOfDocuments) + { + 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; + folder.IsAvailable = request.IsAvailable; + + var result = _context.Folders.Update(folder); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Folder), folder.Id, request.CurrentUser.Id)) + { + _logger.LogAddFolder(folder.Id.ToString(), + folder.Locker.Id.ToString(), + folder.Locker.Room.Id.ToString(), + folder.Locker.Room.Department.Name); + } + + 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/GetAllFoldersPaginated.cs b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs new file mode 100644 index 00000000..d2d9d77d --- /dev/null +++ b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs @@ -0,0 +1,120 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Folders.Queries; + +public class GetAllFoldersPaginated +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.RoomId) + .Must((query, roomId) => roomId is not null || query.LockerId is null); + } + } + + 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; } + 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.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 roomIdProvided = request.RoomId is not null; + var lockerIdProvided = request.LockerId is not null; + + if (lockerIdProvided) + { + var locker = await _context.Lockers + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == request.LockerId + && x.IsAvailable, cancellationToken); + if (locker is null) + { + throw new KeyNotFoundException("Locker does not exist."); + } + + if (locker.Room.Id != request.RoomId) + { + throw new ConflictException("Room does not match locker."); + } + + folders = folders.Where(x => x.Locker.Id == request.LockerId); + } + else if (roomIdProvided) + { + 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."); + } + + folders = folders.Where(x => x.Locker.Room.Id == request.RoomId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + folders = folders.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } + + 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 new file mode 100644 index 00000000..a2183e56 --- /dev/null +++ b/src/Application/Folders/Queries/GetFolderById.cs @@ -0,0 +1,56 @@ +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; + +namespace Application.Folders.Queries; + +public class GetFolderById +{ + public record Query : IRequest + { + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } + public Guid FolderId { 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 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 (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/Helpers/FileUtil.cs b/src/Application/Helpers/FileUtil.cs new file mode 100644 index 00000000..c43e8887 --- /dev/null +++ b/src/Application/Helpers/FileUtil.cs @@ -0,0 +1,7 @@ +namespace Application.Helpers; + +public static class FileUtil +{ + public static long ToByteFromMb(this long sizeInMb) + => sizeInMb * 1024 * 1024; +} \ No newline at end of file diff --git a/src/Application/Helpers/SecurityUtil.cs b/src/Application/Helpers/SecurityUtil.cs index 1ea689af..9936b212 100644 --- a/src/Application/Helpers/SecurityUtil.cs +++ b/src/Application/Helpers/SecurityUtil.cs @@ -19,4 +19,11 @@ public static string Hash(string input) return stringBuilder.ToString(); } + + public static string HashPasswordWith(this string input, string salt, string pepper) + { + pepper = Convert.ToBase64String(Encoding.UTF8.GetBytes(pepper)); + salt = Convert.ToBase64String(Encoding.UTF8.GetBytes(salt)); + return Hash(salt + input + pepper); + } } \ No newline at end of file diff --git a/src/Application/Helpers/StringUtil.cs b/src/Application/Helpers/StringUtil.cs new file mode 100644 index 00000000..ec3c3b74 --- /dev/null +++ b/src/Application/Helpers/StringUtil.cs @@ -0,0 +1,23 @@ +using System.Text; + +namespace Application.Helpers; + +public class StringUtil +{ + private static string alphanumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + public static string RandomString(int n) + { + var stringBuilder = new StringBuilder(); + var length = alphanumeric.Length; + for (var i = 0; i < n; i++) + { + stringBuilder.Append(alphanumeric.ElementAt(new Random().Next(0, length))); + } + + return stringBuilder.ToString(); + } + + public static string RandomPassword() => RandomString(8); + + public static string RandomSalt() => RandomString(24); +} \ No newline at end of file diff --git a/src/Application/Identity/IdentityData.cs b/src/Application/Identity/IdentityData.cs index a9742b72..6e69dbb7 100644 --- a/src/Application/Identity/IdentityData.cs +++ b/src/Application/Identity/IdentityData.cs @@ -2,15 +2,10 @@ namespace Application.Identity; public static class IdentityData { - public static class Claims - { - public const string Role = "role"; - public const string Department = "department"; - } - public static class Roles { public const string Admin = "Admin"; public const string Staff = "Staff"; + public const string Employee = "Employee"; } } \ 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..f93c2dfa --- /dev/null +++ b/src/Application/ImportRequests/Commands/ApproveOrRejectDocument.cs @@ -0,0 +1,136 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + + if (request.Decision.IsApproval()) + { + importRequest.Status = ImportRequestStatus.Approved; + + using (Logging.PushProperties(nameof(Document), document.Id, request.CurrentUser.Id)) + { + _logger.LogApproveImportRequestForDocument(document.Id.ToString()); + } + using (Logging.PushProperties(nameof(ImportRequest), importRequest.Id, request.CurrentUser.Id)) + { + _logger.LogApproveImportRequest(importRequest.Id.ToString()); + } + } + + if (request.Decision.IsRejection()) + { + importRequest.Status = ImportRequestStatus.Rejected; + _context.Documents.Remove(importRequest.Document); + + using (Logging.PushProperties(nameof(Document), document.Id, request.CurrentUser.Id)) + { + _logger.LogRejectImportRequestForDocument(document.Id.ToString()); + } + using (Logging.PushProperties(nameof(ImportRequest), importRequest.Id, request.CurrentUser.Id)) + { + _logger.LogRejectImportRequest(importRequest.Id.ToString()); + } + } + + importRequest.StaffReason = request.StaffReason; + importRequest.LastModified = localDateTimeNow; + importRequest.LastModifiedBy = request.CurrentUser.Id; + + var result = _context.ImportRequests.Update(importRequest); + 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..e134f927 --- /dev/null +++ b/src/Application/ImportRequests/Commands/AssignDocument.cs @@ -0,0 +1,104 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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; + importRequest.Status = ImportRequestStatus.Assigned; + + folder.NumberOfDocuments += 1; + folder.LastModified = localDateTimeNow; + folder.LastModifiedBy = request.CurrentUser.Id; + + _context.Documents.Update(importRequest.Document); + var result = _context.ImportRequests.Update(importRequest); + _context.Folders.Update(folder); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Document), importRequest.Document.Id, request.CurrentUser.Id)) + { + _logger.LogAssignDocument(importRequest.Document.Id.ToString(), folder.Id.ToString()); + } + using (Logging.PushProperties(nameof(Folder), folder.Id, request.CurrentUser.Id)) + { + _logger.LogAssignDocumentToFolder(importRequest.Document.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ 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..fa30b592 --- /dev/null +++ b/src/Application/ImportRequests/Commands/CheckinDocument.cs @@ -0,0 +1,98 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 result = _context.Documents.Update(document); + _context.ImportRequests.Update(importRequest); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Document), document.Id, request.CurrentUser.Id)) + { + _logger.LogCheckinDocument(importRequest.Document.Id.ToString()); + } + using (Logging.PushProperties(nameof(ImportRequest), importRequest.Id, request.CurrentUser.Id)) + { + _logger.LogCheckinImportRequest(importRequest.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + + private static bool StatusesAreNotValid(DocumentStatus documentStatus, ImportRequestStatus importRequestStatus) + => documentStatus is not DocumentStatus.Issued || importRequestStatus is not ImportRequestStatus.Assigned; + } +} \ 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..ef1b088e --- /dev/null +++ b/src/Application/ImportRequests/Commands/RequestImportDocument.cs @@ -0,0 +1,109 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.ImportDocument; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var documentRequest = await _context.ImportRequests + .Include(x => x.Document) + .ThenInclude(x => x.Importer) + .FirstOrDefaultAsync( x => + x.Document.Title.Trim().ToLower().Equals(request.Title.Trim().ToLower()) + && x.Document.Importer!.Id == request.Issuer.Id + && x.Status != ImportRequestStatus.Rejected + , cancellationToken); + + if (documentRequest 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 result = await _context.ImportRequests.AddAsync(importRequest, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Document), result.Entity.DocumentId, request.Issuer.Id)) + { + _logger.LogAddDocument(result.Entity.DocumentId.ToString()); + } + using (Logging.PushProperties(nameof(ImportRequest), importRequest.Id, request.Issuer.Id)) + { + _logger.LogAddDocumentRequest(result.Entity.Id.ToString()); + } + 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..6719e114 --- /dev/null +++ b/src/Application/ImportRequests/Queries/GetAllImportRequestsPaginated.cs @@ -0,0 +1,119 @@ +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 Domain.Statuses; +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[]? Statuses { 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."); + } + } + + var importRequests = _context.ImportRequests + .Include(x => x.Document) + .Include(x => x.Room) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (request.CurrentUser.Role.IsEmployee()) + { + importRequests = importRequests.Where(x => x.CreatedBy! == request.CurrentUser.Id); + if (request.RoomId is not null) + { + 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.RoomId is not null) + { + importRequests = importRequests.Where(x => x.RoomId == request.RoomId); + } + + if (request.Statuses is not null) + { + var statuses = request.Statuses.Aggregate(new List(), + (statuses, currentStatus) => + { + if (!Enum.TryParse(currentStatus, true, out var validStatus)) return statuses; + + statuses.Add(validStatus); + return statuses; + }); + importRequests = importRequests.Where(x => statuses.Contains(x.Status)); + } + + 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..76fe5798 --- /dev/null +++ b/src/Application/ImportRequests/Queries/GetImportRequestById.cs @@ -0,0 +1,58 @@ +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) + .ThenInclude(x => x.Importer) + .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 new file mode 100644 index 00000000..f2123351 --- /dev/null +++ b/src/Application/Lockers/Commands/AddLocker.cs @@ -0,0 +1,120 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Exceptions; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Lockers.Commands; + +public class AddLocker +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Capacity) + .GreaterThan(0).WithMessage("Locker's capacity cannot be less than 1"); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Locker's name is required.") + .MaximumLength(64).WithMessage("Locker's name cannot exceed 64 characters."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Locker's description cannot exceed 256 characters."); + + RuleFor(x => x.RoomId) + .NotEmpty().WithMessage("RoomId is required."); + } + } + + 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; } + public int Capacity { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var room = await _context.Rooms + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + if (room.NumberOfLockers >= room.Capacity) + { + throw new LimitExceededException("This room cannot accept more lockers."); + } + + if (await DuplicatedNameLockerExistsInSameRoomAsync(request.Name, request.RoomId, cancellationToken)) + { + throw new ConflictException("Locker name already exists."); + } + + 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, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var result = await _context.Lockers.AddAsync(entity, cancellationToken); + + room.NumberOfLockers += 1; + _context.Rooms.Update(room); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Locker), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogAddLocker(result.Entity.Id.ToString(), room.Id.ToString(), room.Department.Name); + } + 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/AddLocker/AddLockerCommand.cs b/src/Application/Lockers/Commands/AddLocker/AddLockerCommand.cs deleted file mode 100644 index f23cc079..00000000 --- a/src/Application/Lockers/Commands/AddLocker/AddLockerCommand.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Entities.Physical; -using Domain.Exceptions; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Lockers.Commands.AddLocker; - -public record AddLockerCommand : IRequest -{ - public string Name { get; init; } - public string Description { get; init; } - public Guid RoomId { get; init; } - public int Capacity { get; init; } -} - -public class AddLockerCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public AddLockerCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(AddLockerCommand request, CancellationToken cancellationToken) - { - var room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); - - if (room is null) - { - throw new KeyNotFoundException("Room does not exist."); - } - - if (room.NumberOfLockers >= room.Capacity) - { - throw new LimitExceededException( - "This room cannot accept more lockers." - ); - } - - var locker = await _context.Lockers.FirstOrDefaultAsync(x => x.Name.Trim().Equals(request.Name.Trim()) && x.Room.Id.Equals(request.RoomId)); - if (locker is not null) - { - throw new ConflictException("Locker's name already exists."); - } - - var entity = new Locker - { - Name = request.Name.Trim(), - Description = request.Description, - NumberOfFolders = 0, - Capacity = request.Capacity, - Room = room, - IsAvailable = true - }; - - var result = await _context.Lockers.AddAsync(entity, cancellationToken); - room.NumberOfLockers += 1; - _context.Rooms.Update(room); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/AddLocker/AddLockerCommandValidator.cs b/src/Application/Lockers/Commands/AddLocker/AddLockerCommandValidator.cs deleted file mode 100644 index 4a919d32..00000000 --- a/src/Application/Lockers/Commands/AddLocker/AddLockerCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Application.Common.Interfaces; -using FluentValidation; - -namespace Application.Lockers.Commands.AddLocker; - -public class AddLockerCommandValidator : AbstractValidator -{ - - public AddLockerCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.Capacity) - .GreaterThan(0).WithMessage("Locker's capacity cannot be less than 1"); - - RuleFor(x => x.Name) - .NotEmpty().WithMessage("Locker's name is required.") - .MaximumLength(64).WithMessage("Locker's name cannot exceed 64 characters."); - - RuleFor(x => x.Description) - .MaximumLength(256).WithMessage("Locker's description cannot exceed 256 characters."); - - RuleFor(x => x.RoomId) - .NotEmpty().WithMessage("RoomId is required."); - } -} diff --git a/src/Application/Lockers/Commands/DisableLocker/DisableLockerCommand.cs b/src/Application/Lockers/Commands/DisableLocker/DisableLockerCommand.cs deleted file mode 100644 index e3785e25..00000000 --- a/src/Application/Lockers/Commands/DisableLocker/DisableLockerCommand.cs +++ /dev/null @@ -1,63 +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.Lockers.Commands.DisableLocker; - -public record DisableLockerCommand : IRequest -{ - public Guid LockerId { get; init; } -} - -public class RemoveLockerCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public RemoveLockerCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(DisableLockerCommand request, CancellationToken cancellationToken) - { - var locker = await _context.Lockers - .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/DisableLocker/DisableLockerCommandValidator.cs b/src/Application/Lockers/Commands/DisableLocker/DisableLockerCommandValidator.cs deleted file mode 100644 index e6192be6..00000000 --- a/src/Application/Lockers/Commands/DisableLocker/DisableLockerCommandValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; - -namespace Application.Lockers.Commands.DisableLocker; - -public class DisableLockerCommandValidator : AbstractValidator -{ - public DisableLockerCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.LockerId) - .NotEmpty().WithMessage("Locker Id is required."); - } -} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommand.cs b/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommand.cs deleted file mode 100644 index 4c1cb485..00000000 --- a/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using Domain.Exceptions; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Lockers.Commands.EnableLocker; - -public record EnableLockerCommand : IRequest -{ - public Guid LockerId { get; init; } -} - -public class EnableLockerCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public EnableLockerCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(EnableLockerCommand request, CancellationToken cancellationToken) - { - var locker = await _context.Lockers - .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); - } -} diff --git a/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommandValidator.cs b/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommandValidator.cs deleted file mode 100644 index 2fcbbec9..00000000 --- a/src/Application/Lockers/Commands/EnableLocker/EnableLockerCommandValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; - -namespace Application.Lockers.Commands.EnableLocker; - -public class EnableLockerCommandValidator : AbstractValidator -{ - public EnableLockerCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - RuleFor(x => x.LockerId) - .NotEmpty().WithMessage("Locker Id is required."); - } -} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/RemoveLocker.cs b/src/Application/Lockers/Commands/RemoveLocker.cs new file mode 100644 index 00000000..f5c3cb55 --- /dev/null +++ b/src/Application/Lockers/Commands/RemoveLocker.cs @@ -0,0 +1,84 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Lockers.Commands; + +public class RemoveLocker +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.LockerId) + .NotEmpty().WithMessage("LockerId is required."); + } + } + + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid LockerId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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."); + } + + var canNotRemove = await _context.Documents + .AnyAsync(x => x.Folder!.Locker.Id.Equals(request.LockerId), cancellationToken); + if (canNotRemove) + { + throw new ConflictException("Locker cannot be removed because it contains documents."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var room = locker.Room; + var result = _context.Lockers.Remove(locker); + room.NumberOfLockers -= 1; + _context.Rooms.Update(room); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Locker), locker.Id, request.CurrentUser.Id)) + { + _logger.LogRemoveLocker(locker.Id.ToString(), locker.Room.Id.ToString(), locker.Room.Department.Name); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Lockers/Commands/UpdateLocker.cs b/src/Application/Lockers/Commands/UpdateLocker.cs new file mode 100644 index 00000000..d4db5580 --- /dev/null +++ b/src/Application/Lockers/Commands/UpdateLocker.cs @@ -0,0 +1,118 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Exceptions; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Lockers.Commands; + +public class UpdateLocker +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty() + .MaximumLength(64).WithMessage("Locker's name cannot exceed 64 characters."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Locker's description cannot exceed 256 characters."); + + RuleFor(x => x.Capacity) + .GreaterThan(0).WithMessage("Locker's capacity cannot be less than 1"); + } + } + 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; } + 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; + private readonly ILogger _logger; + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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 (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; + locker.IsAvailable = request.IsAvailable; + + var result = _context.Lockers.Update(locker); + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Locker), locker.Id, request.CurrentUser.Id)) + { + _logger.LogUpdateLocker(locker.Id.ToString(), locker.Room.Id.ToString(), locker.Room.Department.Name); + } + + 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/GetAllLockersPaginated.cs b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs new file mode 100644 index 00000000..24d1645b --- /dev/null +++ b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs @@ -0,0 +1,101 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Lockers.Queries; + +public class GetAllLockersPaginated +{ + public record Query : IRequest> + { + public Guid CurrentUserId { get; init; } + 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; } + 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.CurrentUserRole.IsStaff()) + { + if (request.RoomId is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + var currentStaffRoom = await GetRoomByStaffIdAsync(request.CurrentUserId, cancellationToken); + + if (currentStaffRoom is null) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (!IsSameRoom(currentStaffRoom.Id, request.RoomId.Value)) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + } + + var lockers = _context.Lockers + .Include(x => x.Room) + .ThenInclude(y => y.Department) + .AsQueryable(); + + if (request.RoomId is not null) + { + lockers = lockers.Where(x => x.Room.Id == request.RoomId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + lockers = lockers.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } + + return await lockers + .ListPaginateWithSortAsync( + request.Page, + request.Size, + request.SortBy, + request.SortOrder, + _mapper.ConfigurationProvider, + cancellationToken); + } + + private async Task GetRoomByStaffIdAsync(Guid departmentId, CancellationToken cancellationToken) + { + var staff = await _context.Staffs + .Include(x => x.Room) + .FirstOrDefaultAsync(x => x.Id == departmentId, cancellationToken); + + return staff?.Room; + } + + 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 new file mode 100644 index 00000000..3a21a7a4 --- /dev/null +++ b/src/Application/Lockers/Queries/GetLockerById.cs @@ -0,0 +1,58 @@ +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; + +namespace Application.Lockers.Queries; + +public class GetLockerById +{ + public record Query : IRequest + { + public string CurrentUserRole { get; init; } = null!; + public Guid? CurrentStaffRoomId { get; init; } + public Guid LockerId { 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 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 (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/Loggings/Queries/GetAllLogsPaginated.cs b/src/Application/Loggings/Queries/GetAllLogsPaginated.cs new file mode 100644 index 00000000..9ee82c0d --- /dev/null +++ b/src/Application/Loggings/Queries/GetAllLogsPaginated.cs @@ -0,0 +1,79 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Users.Queries; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Logs.Queries; + +public class GetAllLogsPaginated +{ + public record Query : IRequest> + { + public Guid? ObjectId { get; init; } + public string? ObjectType { 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.Logs.AsQueryable(); + + if (request.ObjectType is not null) + { + logs = logs.Where(x => x.ObjectType!.Equals(request.ObjectType)); + } + + if (request.ObjectId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.ObjectId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => x.Message!.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 ? 10 : 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 = list.Select(x => new LogDto() + { + Id = x.Id, + Event = x.Event, + Template = x.Template, + Message = x.Message, + Level = x.Level, + ObjectType = x.ObjectType, + User = _mapper.Map(_context.Users.FirstOrDefault(y => y.Id == x.UserId!)), + ObjectId = x.ObjectId!, + Time = x.Time?.ToDateTimeUtc(), + }) + .ToList(); + + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } + } +} \ No newline at end of file diff --git a/src/Application/Loggings/Queries/GetAllRequestLogsPaginated.cs b/src/Application/Loggings/Queries/GetAllRequestLogsPaginated.cs new file mode 100644 index 00000000..6f145ee1 --- /dev/null +++ b/src/Application/Loggings/Queries/GetAllRequestLogsPaginated.cs @@ -0,0 +1,76 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Users.Queries; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Loggings.Queries; + +public class GetAllRequestLogsPaginated +{ + public record Query : IRequest> + { + public Guid? BorrowRequestId { 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.Logs + .Where(x => x.ObjectType!.Equals("BorrowRequest")) + .AsQueryable(); + + if (request.BorrowRequestId is not null) + { + logs = logs.Where(x => x.ObjectId! == request.BorrowRequestId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + logs = logs.Where(x => + x.Message!.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 ? 10 : 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 = list.Select(x => new LogDto() + { + Id = x.Id, + Event = x.Event, + Template = x.Template, + Message = x.Message, + Level = x.Level, + ObjectType = x.ObjectType, + User = _mapper.Map(_context.Users.FirstOrDefault(y => y.Id == x.UserId!)), + ObjectId = x.ObjectId!, + Time = x.Time?.ToDateTimeUtc(), + }) + .ToList(); + + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } + } +} diff --git a/src/Application/Rooms/Commands/AddRoom.cs b/src/Application/Rooms/Commands/AddRoom.cs new file mode 100644 index 00000000..e6692789 --- /dev/null +++ b/src/Application/Rooms/Commands/AddRoom.cs @@ -0,0 +1,113 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; +using Serilog.Context; + +namespace Application.Rooms.Commands; + +public class AddRoom +{ + public class Validator : AbstractValidator + { + private readonly IApplicationDbContext _context; + public Validator(IApplicationDbContext context) + { + _context = context; + + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Capacity) + .GreaterThanOrEqualTo(1).WithMessage("Room's capacity cannot be less than 1"); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Name is required.") + .MaximumLength(64).WithMessage("Name cannot exceed 64 characters.") + .Must(BeUnique).WithMessage("Room name already exists."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); + } + + private bool BeUnique(string name) + { + return _context.Rooms.FirstOrDefault(x => x.Name.Equals(name)) is null; + } + } + + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public string Name { get; init; } = null!; + public string? Description { get; init; } + public int Capacity { get; init; } + public Guid DepartmentId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var department = + await _context.Departments.FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); + + if (department is null) + { + throw new KeyNotFoundException("Department does not exists."); + } + + var room = await _context.Rooms.FirstOrDefaultAsync(r => + r.Name.Trim().ToLower().Equals(request.Name.Trim().ToLower()), cancellationToken); + + if (room is not null) + { + throw new ConflictException("Room name already exists."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + var entity = new Room + { + Name = request.Name.Trim(), + Description = request.Description?.Trim(), + NumberOfLockers = 0, + Capacity = request.Capacity, + Department = department, + DepartmentId = request.DepartmentId, + IsAvailable = true, + Created = localDateTimeNow, + CreatedBy = request.CurrentUser.Id, + }; + + var result = await _context.Rooms.AddAsync(entity, cancellationToken); + + using (Logging.PushProperties(nameof(Room), entity.Id, request.CurrentUser.Id)) + { + _logger.LogAddRoom(result.Entity.Id.ToString(), department.Name); + } + + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommand.cs b/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommand.cs deleted file mode 100644 index 53c24c4d..00000000 --- a/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommand.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using Application.Users.Queries; -using AutoMapper; -using Domain.Entities.Physical; -using MediatR; - -namespace Application.Rooms.Commands.CreateRoom; - -public record CreateRoomCommand : IRequest -{ - public string Name { get; init; } - public string Description { get; init; } - public int Capacity { get; init; } - -} - -public class CreateRoomCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public CreateRoomCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(CreateRoomCommand request, CancellationToken cancellationToken) - { - var entity = new Room - { - Name = request.Name, - Description = request.Description, - NumberOfLockers = 0, - Capacity = request.Capacity - }; - var result = await _context.Rooms.AddAsync(entity, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommandValidator.cs b/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommandValidator.cs deleted file mode 100644 index 14bfc7ca..00000000 --- a/src/Application/Rooms/Commands/CreateRoom/CreateRoomCommandValidator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Application.Common.Interfaces; -using FluentValidation; - -namespace Application.Rooms.Commands.CreateRoom; - -public class CreateRoomCommandValidator : AbstractValidator -{ - private readonly IApplicationDbContext _context; - public CreateRoomCommandValidator(IApplicationDbContext context) - { - _context = context; - - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.Capacity) - .GreaterThanOrEqualTo(1).WithMessage("Room's capacity cannot be less than 1"); - - RuleFor(x => x.Name) - .NotEmpty().WithMessage("Name is required.") - .MaximumLength(64).WithMessage("Name cannot exceed 64 characters.") - .Must(BeUnique).WithMessage("Room name already exists."); - - RuleFor(x => x.Description) - .NotEmpty().WithMessage("Description is required.") - .MaximumLength(256).WithMessage("Description cannot exceed 256 characters."); - } - - private bool BeUnique(string name) - { - return _context.Rooms.FirstOrDefault(x => x.Name.Equals(name)) is null; - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/DisableRoom/DisableRoomCommand.cs b/src/Application/Rooms/Commands/DisableRoom/DisableRoomCommand.cs deleted file mode 100644 index dbf28e54..00000000 --- a/src/Application/Rooms/Commands/DisableRoom/DisableRoomCommand.cs +++ /dev/null @@ -1,69 +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.Rooms.Commands.DisableRoom; - -public record DisableRoomCommand : IRequest -{ - public Guid RoomId { get; init; } -} - -public class DisableRoomCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public DisableRoomCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(DisableRoomCommand request, CancellationToken cancellationToken) - { - var room = await _context.Rooms - .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 InvalidOperationException("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/DisableRoom/DisableRoomCommandValidator.cs b/src/Application/Rooms/Commands/DisableRoom/DisableRoomCommandValidator.cs deleted file mode 100644 index de317f88..00000000 --- a/src/Application/Rooms/Commands/DisableRoom/DisableRoomCommandValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; - -namespace Application.Rooms.Commands.DisableRoom; - -public class DisableRoomCommandValidator : AbstractValidator -{ - public DisableRoomCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.RoomId) - .NotEmpty().WithMessage("RoomId is required."); - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/RemoveRoom.cs b/src/Application/Rooms/Commands/RemoveRoom.cs new file mode 100644 index 00000000..73d7579c --- /dev/null +++ b/src/Application/Rooms/Commands/RemoveRoom.cs @@ -0,0 +1,87 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Rooms.Commands; + +public class RemoveRoom +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.RoomId) + .NotEmpty().WithMessage("RoomId is required."); + } + } + + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid RoomId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + 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 ConflictException("Room cannot be removed because it contains something."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var result = _context.Rooms.Remove(room); + + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Room), room.Id, request.CurrentUser.Id)) + { + _logger.LogRemoveRoom(room.Id.ToString(), room.Department.Name); + } + + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommand.cs b/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommand.cs deleted file mode 100644 index 43e7f575..00000000 --- a/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommand.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models.Dtos.Physical; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Rooms.Commands.RemoveRoom; - -public record RemoveRoomCommand : IRequest -{ - public Guid RoomId { get; init; } -} - -public class RemoveRoomCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public RemoveRoomCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(RemoveRoomCommand request, CancellationToken cancellationToken) - { - var room = await _context.Rooms - .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: 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) - { - throw new InvalidOperationException("Room cannot be removed because it contains documents."); - } - - room.IsAvailable = false; - var result = _context.Rooms.Remove(room); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommandValidator.cs b/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommandValidator.cs deleted file mode 100644 index d138ba88..00000000 --- a/src/Application/Rooms/Commands/RemoveRoom/RemoveRoomCommandValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; - -namespace Application.Rooms.Commands.RemoveRoom; - -public class RemoveRoomCommandValidator : AbstractValidator -{ - public RemoveRoomCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.RoomId) - .NotEmpty().WithMessage("RoomId is required."); - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Commands/UpdateRoom.cs b/src/Application/Rooms/Commands/UpdateRoom.cs new file mode 100644 index 00000000..0cdea5ff --- /dev/null +++ b/src/Application/Rooms/Commands/UpdateRoom.cs @@ -0,0 +1,119 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Rooms.Commands; + +public class UpdateRoom +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("Name can not be empty.") + .MaximumLength(64).WithMessage("Name can not exceed 64 characters."); + + RuleFor(x => x.Capacity) + .NotEmpty().WithMessage("Capacity can not be empty."); + + RuleFor(x => x.Description) + .MaximumLength(256).WithMessage("Description can not exceed 256 characters."); + } + } + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + 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) + { + throw new KeyNotFoundException("Room does not exist."); + } + + if (await DuplicatedNameRoomExistsAsync(request.Name, request.RoomId, cancellationToken)) + { + throw new ConflictException("Name has already exists."); + } + + if (request.Capacity < room.NumberOfLockers) + { + throw new ConflictException("New capacity cannot be less than current number of lockers."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + // 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; + + + _context.Rooms.Entry(room).State = EntityState.Modified; + + await _context.SaveChangesAsync(cancellationToken); + + using (Logging.PushProperties(nameof(Room), room.Id, request.CurrentUser.Id)) + { + _logger.LogUpdateRoom(room.Id.ToString(), room.Department.Name); + } + + 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/GetAllRoomsPaginated.cs b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs new file mode 100644 index 00000000..fa6c7615 --- /dev/null +++ b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs @@ -0,0 +1,79 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetAllRoomsPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public Guid? DepartmentId { get; init; } + public bool? IsAvailable { 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 rooms = _context.Rooms + .Include(x => x.Department) + .Include(x => x.Staff) + .AsQueryable(); + + if ((request.CurrentUser.Role.IsStaff() || request.CurrentUser.Role.IsEmployee()) + && request.CurrentUser.Department?.Id != request.DepartmentId) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + if (request.DepartmentId is not null) + { + rooms = rooms.Where(x => x.Department.Id == request.DepartmentId); + } + + if (request.IsAvailable is not null) + { + rooms = rooms.Where(x => x.IsAvailable == request.IsAvailable); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + rooms = rooms.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } + + 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 new file mode 100644 index 00000000..96157358 --- /dev/null +++ b/src/Application/Rooms/Queries/GetEmptyContainersPaginated.cs @@ -0,0 +1,54 @@ +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetEmptyContainersPaginated +{ + public record Query : IRequest> + { + public Guid RoomId { 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 room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + var pageNumber = request.Page ?? 1; + var sizeNumber = request.Size ?? 5; + var lockers = _context.Lockers + .Where(x => x.Room.Id == request.RoomId + && x.IsAvailable + && x.Folders.Any(y => y.Capacity > y.NumberOfDocuments && y.IsAvailable)) + .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); + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetEmptyContainersPaginated/GetEmptyContainersPaginatedQuery.cs b/src/Application/Rooms/Queries/GetEmptyContainersPaginated/GetEmptyContainersPaginatedQuery.cs deleted file mode 100644 index dcd6cbf5..00000000 --- a/src/Application/Rooms/Queries/GetEmptyContainersPaginated/GetEmptyContainersPaginatedQuery.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Models; -using AutoMapper; -using AutoMapper.QueryableExtensions; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Rooms.Queries.GetEmptyContainersPaginated; - -public record GetEmptyContainersPaginatedQuery : IRequest> -{ - public Guid RoomId { get; init; } - public int Page { get; init; } - public int Size { get; init; } -} - -public class GetEmptyContainersPaginatedQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public GetEmptyContainersPaginatedQueryHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task> Handle(GetEmptyContainersPaginatedQuery request, CancellationToken cancellationToken) - { - var room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); - if (room is null) - { - throw new KeyNotFoundException("Room does not exist"); - } - - var lockers = _context.Lockers - .Where(x => x.Room.Id == request.RoomId - && x.IsAvailable - && x.Folders.Any(y => y.Capacity > y.NumberOfDocuments && y.IsAvailable)) - .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(), request.Page, request.Size); - return result; - } -} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs b/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs new file mode 100644 index 00000000..7a3aa1de --- /dev/null +++ b/src/Application/Rooms/Queries/GetRoomByDepartmentId.cs @@ -0,0 +1,61 @@ +using System.Collections.ObjectModel; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +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 string CurrentUserRole { get; init; } = null!; + public Guid CurrentUserDepartmentId { get; init; } + 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) + { + + if ((request.CurrentUserRole.IsEmployee() + || request.CurrentUserRole.IsStaff()) + && request.CurrentUserDepartmentId != request.DepartmentId) + { + throw new UnauthorizedAccessException("User cannot access this resource."); + } + + var department = await _context.Departments + .FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); + + if (department is null) + { + throw new KeyNotFoundException("Department does not exist."); + } + + var rooms = _context.Rooms + .Include(x => x.Department) + .Include(x => x.Staff) + .Where(x => x.DepartmentId == request.DepartmentId); + + var result = new ItemsResult + (new ReadOnlyCollection(_mapper.Map>(rooms))); + return result; + } + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetRoomById.cs b/src/Application/Rooms/Queries/GetRoomById.cs new file mode 100644 index 00000000..76532fbe --- /dev/null +++ b/src/Application/Rooms/Queries/GetRoomById.cs @@ -0,0 +1,55 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Rooms.Queries; + +public class GetRoomById +{ + public record Query : IRequest + { + public string CurrentUserRole { get; init; } = null!; + public Guid CurrentUserDepartmentId { get; init; } + public Guid RoomId { 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.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..737ac602 --- /dev/null +++ b/src/Application/Rooms/Queries/GetRoomByStaffId.cs @@ -0,0 +1,54 @@ +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.Rooms.Queries; + +public class GetRoomByStaffId +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + 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/AssignStaff.cs b/src/Application/Staffs/Commands/AssignStaff.cs new file mode 100644 index 00000000..32f99ee9 --- /dev/null +++ b/src/Application/Staffs/Commands/AssignStaff.cs @@ -0,0 +1,102 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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) + .Include(x => x.Department) + .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 user = await _context.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == staff.Id, cancellationToken); + + if (user is null) + { + throw new KeyNotFoundException("User does not exist."); + } + + if (room.Department.Id != user.Department!.Id) + { + throw new ConflictException("Room is in different department."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + staff.Room = room; + room.Staff = staff; + room.LastModified = localDateTimeNow; + room.LastModifiedBy = request.CurrentUser.Id; + + _context.Rooms.Update(room); + var result = _context.Staffs.Update(staff); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(Staff), staff.Id, request.CurrentUser.Id)) + { + _logger.LogAssignStaff(staff.Id.ToString(), staff.Room.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/CreateStaff/CreateStaffCommand.cs b/src/Application/Staffs/Commands/CreateStaff/CreateStaffCommand.cs deleted file mode 100644 index bf5fbfa1..00000000 --- a/src/Application/Staffs/Commands/CreateStaff/CreateStaffCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Application.Common.Interfaces; -using Application.Users.Queries.Physical; -using AutoMapper; -using Domain.Entities.Physical; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Staffs.Commands.CreateStaff; - -public record CreateStaffCommand : IRequest -{ - public Guid UserId { get; init; } - public Guid RoomId { get; init; } -} - -public class CreateStaffCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - - public CreateStaffCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(CreateStaffCommand 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 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/RemoveStaffFromRoom.cs b/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs new file mode 100644 index 00000000..fdd95573 --- /dev/null +++ b/src/Application/Staffs/Commands/RemoveStaffFromRoom.cs @@ -0,0 +1,72 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Staffs.Commands; + +public class RemoveStaffFromRoom +{ + public record Command : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid StaffId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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."); + } + + if (staff.Room is null) + { + throw new ConflictException("Staff is not assigned to a room."); + } + + var room = staff.Room; + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + staff.Room.Staff = null; + _context.Rooms.Update(staff.Room!); + staff.Room = null; + + var result = _context.Staffs.Update(staff); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(User), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogRemoveStaffFromRoom(result.Entity.Id.ToString(), room.Id.ToString()); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..c0987de1 --- /dev/null +++ b/src/Application/Staffs/Queries/GetAllStaffsPaginated.cs @@ -0,0 +1,59 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Domain.Entities.Physical; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Staffs.Queries; + +public class GetAllStaffsPaginated +{ + public class Query : IRequest> + { + public string? SearchTerm { get; set; } + public int? Page { get; set; } + public int? Size { get; set; } + public string? SortBy { get; set; } + public string? SortOrder { get; set; } + } + + 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 staffs = _context.Staffs + .Include(x => x.Room) + .Include(x => x.User) + .ThenInclude(x => x.Department) + .AsQueryable(); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + staffs = staffs.Where(x => x.User.Username.ToLower().Contains(request.SearchTerm.ToLower())); + } + + 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/GetStaffById.cs b/src/Application/Staffs/Queries/GetStaffById.cs new file mode 100644 index 00000000..6cab8c90 --- /dev/null +++ b/src/Application/Staffs/Queries/GetStaffById.cs @@ -0,0 +1,42 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Staffs.Queries; + +public class GetStaffById +{ + 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 + .Include(x => x.User) + .Include(y => y.Room) + .FirstOrDefaultAsync(x => x.Id.Equals(request.StaffId), cancellationToken: cancellationToken); + + if (staff is null) + { + throw new KeyNotFoundException("Staff does not exist."); + } + + return _mapper.Map(staff); + } + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Queries/GetStaffByRoomId.cs b/src/Application/Staffs/Queries/GetStaffByRoomId.cs new file mode 100644 index 00000000..119b96f1 --- /dev/null +++ b/src/Application/Staffs/Queries/GetStaffByRoomId.cs @@ -0,0 +1,57 @@ +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 GetStaffByRoomId +{ + public record Query : IRequest + { + public User CurrentUser { get; init; } = null!; + public Guid RoomId { 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) + .ThenInclude(x => x.User) + .FirstOrDefaultAsync(x => x.Id.Equals(request.RoomId), cancellationToken: cancellationToken); + + if (room is null) + { + 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."); + } + + return _mapper.Map(room.Staff); + } + } +} \ No newline at end of file diff --git a/src/Application/Users/Commands/AddUser.cs b/src/Application/Users/Commands/AddUser.cs new file mode 100644 index 00000000..17dd5b03 --- /dev/null +++ b/src/Application/Users/Commands/AddUser.cs @@ -0,0 +1,156 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Helpers; +using Application.Identity; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities; +using Domain.Events; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; +using Serilog.Context; +using Serilog.Core.Enrichers; + +namespace Application.Users.Commands; + +public class AddUser +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Username) + .NotEmpty().WithMessage("Username is required.") + .MaximumLength(50).WithMessage("Username cannot exceed 64 characters."); + + RuleFor(x => x.Email) + .NotEmpty().WithMessage("Email is required.") + .EmailAddress().WithMessage("Require valid email.") + .MaximumLength(320).WithMessage("Email length too long."); + + RuleFor(x => x.Role) + .NotEmpty().WithMessage("Role is required.") + .MaximumLength(64).WithMessage("Role cannot exceed 64 characters.") + .Must(BeNotAdmin).WithMessage("Cannot add a user as Administrator."); + + RuleFor(x => x.FirstName) + .MaximumLength(50).WithMessage("First name cannot exceed 50 characters."); + + RuleFor(x => x.LastName) + .MaximumLength(50).WithMessage("Last name cannot exceed 50 characters."); + + RuleFor(x => x.Position) + .MaximumLength(64).WithMessage("Position cannot exceed 64 characters."); + } + + private static bool BeNotAdmin(string role) + { + return !role.Equals(IdentityData.Roles.Admin); + } + } + + 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; } + public string? LastName { get; init; } + public Guid DepartmentId { get; init; } + public string Role { get; init; } = null!; + public string? Position { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly ISecurityService _securityService; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, ISecurityService securityService, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _securityService = securityService; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + + if (user is not null) + { + throw new ConflictException("Username or Email has been taken."); + } + + var department = await _context.Departments + .FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); + + if (department is null) + { + throw new KeyNotFoundException("Department does not exist."); + } + + if (!request.Role.Trim().Equals(IdentityData.Roles.Staff) && + !request.Role.Trim().Equals(IdentityData.Roles.Employee)) + { + throw new ConflictException("Invalid role provided."); + } + + var password = StringUtil.RandomPassword(); + var salt = StringUtil.RandomSalt(); + + var localDateTimeNow = LocalDateTime.FromDateTime(_dateTimeProvider.DateTimeNow); + + var entity = new User + { + Username = request.Username, + PasswordHash = _securityService.Hash(password, salt), + PasswordSalt = salt, + Email = request.Email, + FirstName = request.FirstName?.Trim(), + LastName = request.LastName?.Trim(), + Department = department, + Role = request.Role, + Position = request.Position, + IsActive = true, + IsActivated = false, + 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); + + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(User), entity.Id, request.CurrentUser.Id)) + { + _logger.LogAddUser(result.Entity.Username, result.Entity.Email, result.Entity.Role); + } + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Users/Commands/CreateUser/CreateUserCommand.cs b/src/Application/Users/Commands/CreateUser/CreateUserCommand.cs deleted file mode 100644 index c0ed1617..00000000 --- a/src/Application/Users/Commands/CreateUser/CreateUserCommand.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Application.Common.Exceptions; -using Application.Common.Interfaces; -using Application.Helpers; -using Application.Users.Queries; -using AutoMapper; -using Domain.Entities; -using Domain.Events; -using MediatR; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace Application.Users.Commands.CreateUser; - -public record CreateUserCommand : IRequest -{ - public string Username { get; init; } - public string Email { get; init; } - public string Password { get; init; } - public string FirstName { get; init; } - public string LastName { get; init; } - public Guid DepartmentId { get; init; } - public string Role { get; init; } - public string Position { get; init; } -} - -public class CreateUserCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public CreateUserCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken) - { - var user = await _context.Users.FirstOrDefaultAsync( - x => x.Username.Equals(request.Username) || x.Email.Equals(request.Email), cancellationToken); - - if (user is not null) - { - throw new ConflictException("Username or Email has been taken"); - } - - var department = await _context.Departments - .FirstOrDefaultAsync(x => x.Id == request.DepartmentId, cancellationToken); - - if (department is null) - { - throw new KeyNotFoundException("Department does not exist"); - } - - var entity = new User - { - Username = request.Username, - PasswordHash = SecurityUtil.Hash(request.Password), - Email = request.Email, - FirstName = request.FirstName.Trim(), - LastName = request.LastName.Trim(), - Department = department, - Role = request.Role, - Position = request.Position, - IsActive = true, - IsActivated = false, - Created = LocalDateTime.FromDateTime(DateTime.UtcNow) - }; - entity.AddDomainEvent(new UserCreatedEvent(entity)); - var result = await _context.Users.AddAsync(entity, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); - } -} - diff --git a/src/Application/Users/Commands/CreateUser/CreateUserCommandValidator.cs b/src/Application/Users/Commands/CreateUser/CreateUserCommandValidator.cs deleted file mode 100644 index cfd82b1d..00000000 --- a/src/Application/Users/Commands/CreateUser/CreateUserCommandValidator.cs +++ /dev/null @@ -1,44 +0,0 @@ -using FluentValidation; - -namespace Application.Users.Commands.CreateUser; - -public class CreateUserCommandValidator : AbstractValidator -{ - public CreateUserCommandValidator() - { - RuleLevelCascadeMode = CascadeMode.Stop; - - RuleFor(x => x.Username) - .NotEmpty().WithMessage("Username is required.") - .MaximumLength(50).WithMessage("Username cannot exceed 64 characters."); - - RuleFor(x => x.Email) - .EmailAddress().WithMessage("Require valid email.") - .MaximumLength(320).WithMessage("Email length too long"); - - RuleFor(x => x.Password) - .NotEmpty().WithMessage("Your password cannot be empty"); - - RuleFor(x => x.Role) - .NotEmpty().WithMessage("Role cannot be empty.") - .MaximumLength(64).WithMessage("Role cannot exceed 64 characters.") - .Must(BeNotAdmin).WithMessage("Cannot add a user as Administrator."); - - RuleFor(x => x.FirstName) - .NotEmpty().WithMessage("First name is required.") - .MaximumLength(50).WithMessage("First name cannot exceed 50 characters."); - - RuleFor(x => x.LastName) - .NotEmpty().WithMessage("Last name is required.") - .MaximumLength(50).WithMessage("Last name cannot exceed 50 characters."); - - RuleFor(x => x.Position) - .NotEmpty().WithMessage("Position cannot be empty.") - .MaximumLength(64).WithMessage("Position cannot exceed 64 characters."); - } - - private static bool BeNotAdmin(string role) - { - return !role.Equals("Administrator"); - } -} \ No newline at end of file diff --git a/src/Application/Users/Commands/DisableUser/DisableUserCommand.cs b/src/Application/Users/Commands/DisableUser/DisableUserCommand.cs deleted file mode 100644 index 505961e6..00000000 --- a/src/Application/Users/Commands/DisableUser/DisableUserCommand.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Application.Common.Interfaces; -using Application.Users.Queries; -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace Application.Users.Commands.DisableUser; - -public record DisableUserCommand : IRequest -{ - public Guid UserId { get; init; } -} - -public class DisableUserCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public DisableUserCommandHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task Handle(DisableUserCommand 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 InvalidOperationException("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/UpdateUser.cs b/src/Application/Users/Commands/UpdateUser.cs new file mode 100644 index 00000000..584f57d2 --- /dev/null +++ b/src/Application/Users/Commands/UpdateUser.cs @@ -0,0 +1,99 @@ +using Application.Common.Extensions; +using Application.Common.Extensions.Logging; +using Application.Common.Interfaces; +using Application.Common.Logging; +using Application.Common.Messages; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Users.Commands; + +public class UpdateUser +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.FirstName) + .MaximumLength(50).WithMessage("FirstName can not exceed 50 characters."); + + RuleFor(x => x.LastName) + .MaximumLength(50).WithMessage("LastName can not exceed 50 characters."); + + RuleFor(x => x.Position) + .MaximumLength(64).WithMessage("Position can not exceed 64 characters."); + } + } + 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; + private readonly ILogger _logger; + + public CommandHandler(IApplicationDbContext context, IMapper mapper, IDateTimeProvider dateTimeProvider, ILogger logger) + { + _context = context; + _mapper = mapper; + _dateTimeProvider = dateTimeProvider; + _logger = logger; + } + + 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); + + if (user is null) + { + 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 result = _context.Users.Update(user); + await _context.SaveChangesAsync(cancellationToken); + using (Logging.PushProperties(nameof(User), result.Entity.Id, request.CurrentUser.Id)) + { + _logger.LogUpdateUser(result.Entity.Username); + } + 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/EventHandlers/UserCreatedEventHandler.cs b/src/Application/Users/EventHandlers/UserCreatedEventHandler.cs new file mode 100644 index 00000000..41b48e34 --- /dev/null +++ b/src/Application/Users/EventHandlers/UserCreatedEventHandler.cs @@ -0,0 +1,40 @@ +using Application.Common.Interfaces; +using Application.Helpers; +using Domain.Entities; +using Domain.Events; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Users.EventHandlers; + +public class UserCreatedEventHandler : INotificationHandler +{ + private readonly IMailService _mailService; + private readonly IAuthDbContext _authDbContext; + + public UserCreatedEventHandler(IMailService mailService, IAuthDbContext authDbContext) + { + _mailService = mailService; + _authDbContext = authDbContext; + } + + public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken) + { + var expirationDate = LocalDateTime.FromDateTime(DateTime.Now.AddDays(1)); + + var token = Guid.NewGuid().ToString(); + var resetPasswordToken = new ResetPasswordToken() + { + User = notification.User, + TokenHash = SecurityUtil.Hash(token), + ExpirationDate = expirationDate, + IsInvalidated = false, + }; + + await _authDbContext.ResetPasswordTokens.AddAsync(resetPasswordToken, cancellationToken); + await _authDbContext.SaveChangesAsync(cancellationToken); + + _mailService.SendResetPasswordHtmlMail(notification.User.Email, notification.Password, token); + } +} \ No newline at end of file diff --git a/src/Application/Users/Queries/GetAllSharedUsersOfASharedEntryPaginated.cs b/src/Application/Users/Queries/GetAllSharedUsersOfASharedEntryPaginated.cs new file mode 100644 index 00000000..29d86754 --- /dev/null +++ b/src/Application/Users/Queries/GetAllSharedUsersOfASharedEntryPaginated.cs @@ -0,0 +1,79 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Digital; +using Application.Common.Models.Operations; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Digital; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Users.Queries; + +public class GetAllSharedUsersOfASharedEntryPaginated +{ + public record Query : IRequest> + { + public User CurrentUser { get; init; } = null!; + public Guid EntryId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + } + + public class Handler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public Handler(IMapper mapper, IApplicationDbContext context) + { + _mapper = mapper; + _context = context; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var entry = await _context.Entries + .FirstOrDefaultAsync(x => x.Id == request.EntryId, cancellationToken); + + if (entry is null) + { + throw new KeyNotFoundException("Entry does not exist."); + } + + var permission = await _context.EntryPermissions + .FirstOrDefaultAsync(x => x.EntryId == request.EntryId + && x.EmployeeId == request.CurrentUser.Id, cancellationToken); + + if ((permission is null || !permission.AllowedOperations.Contains(EntryOperation.View.ToString())) && + (entry.OwnerId != request.CurrentUser.Id)) + { + throw new UnauthorizedAccessException("You do not have permission to view this shared entry's users."); + } + + var permissions = _context.EntryPermissions + .Include(x => x.Employee) + .Where(x => x.EntryId == request.EntryId); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + permissions = permissions.Where(x => + x.Employee.Username.ToLower().Trim().Contains(request.SearchTerm.ToLower().Trim())); + } + + var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; + var sizeNumber = request.Size is null or <= 0 ? 10 : request.Size; + + var count = await permissions.CountAsync(cancellationToken); + var list = await permissions + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + 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/Users/Queries/GetAllUsersPaginated.cs b/src/Application/Users/Queries/GetAllUsersPaginated.cs new file mode 100644 index 00000000..9fb451ad --- /dev/null +++ b/src/Application/Users/Queries/GetAllUsersPaginated.cs @@ -0,0 +1,71 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Identity; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Users.Queries; + +public class GetAllUsersPaginated +{ + public record Query : IRequest> + { + 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; } + 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 users = _context.Users + .Include(x => x.Department) + .Where(x => !x.Role.Equals(IdentityData.Roles.Admin)); + + // Filter by department + if (request.DepartmentIds is not null) + { + 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())); + } + + 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 new file mode 100644 index 00000000..d49ddb29 --- /dev/null +++ b/src/Application/Users/Queries/GetUserById.cs @@ -0,0 +1,54 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Identity; +using AutoMapper; +using Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Users.Queries; + +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; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var user = await _context.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id.Equals(request.UserId), cancellationToken: cancellationToken); + + if (user is null) + { + 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/Application/Users/Queries/GetUsersByName/GetUsersByNameQuery.cs b/src/Application/Users/Queries/GetUsersByName/GetUsersByNameQuery.cs deleted file mode 100644 index 76604efb..00000000 --- a/src/Application/Users/Queries/GetUsersByName/GetUsersByNameQuery.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Application.Common.Interfaces; -using Application.Common.Mappings; -using Application.Common.Models; -using AutoMapper; -using AutoMapper.QueryableExtensions; -using MediatR; - -namespace Application.Users.Queries.GetUsersByName; - -public record GetUsersByNameQuery : IRequest> -{ - public string? SearchTerm { get; init; } - public int? Page { get; init; } - public int? Size { get; init; } -} - -public class GetUsersByNameQueryHandler : IRequestHandler> -{ - private readonly IApplicationDbContext _context; - private readonly IMapper _mapper; - public GetUsersByNameQueryHandler(IApplicationDbContext context, IMapper mapper) - { - _context = context; - _mapper = mapper; - } - - public async Task> Handle(GetUsersByNameQuery request, CancellationToken cancellationToken) - { - var pageNumber = request.Page ?? 1; - var sizeNumber = request.Size ?? 5; - var users = await _context.Users - .Where(x => string.IsNullOrEmpty(request.SearchTerm) - || x.FirstName.ToLower().Contains(request.SearchTerm.ToLower()) - || x.LastName.ToLower().Contains(request.SearchTerm.ToLower())) - .ProjectTo(_mapper.ConfigurationProvider) - .OrderBy(x => x.Username) - .PaginatedListAsync(pageNumber, sizeNumber); - return users; - } -} \ 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 6513aa25..6a353b38 100644 --- a/src/Domain/Entities/Department.cs +++ b/src/Domain/Entities/Department.cs @@ -1,8 +1,11 @@ using Domain.Common; +using Domain.Entities.Physical; namespace Domain.Entities; public class Department : BaseEntity { public string Name { get; set; } = null!; + + public ICollection Rooms { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Domain/Entities/Digital/Entry.cs b/src/Domain/Entities/Digital/Entry.cs new file mode 100644 index 00000000..2d7212d1 --- /dev/null +++ b/src/Domain/Entities/Digital/Entry.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Domain.Common; + +namespace Domain.Entities.Digital; + +public class Entry : BaseAuditableEntity +{ + public string Name { get; set; } = null!; + public string Path { get; set; } = null!; + public string? OldPath { get; set; } + public Guid? FileId { get; set; } + public Guid OwnerId { get; set; } + public long? SizeInBytes { get; set; } + [NotMapped] public bool IsDirectory => FileId is null; + [NotMapped] public bool IsInBin => OldPath is not null; + + public virtual FileEntity? File { get; set; } + public virtual User Uploader { get; set; } = null!; + public virtual User Owner { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Domain/Entities/Digital/EntryPermission.cs b/src/Domain/Entities/Digital/EntryPermission.cs new file mode 100644 index 00000000..2949b379 --- /dev/null +++ b/src/Domain/Entities/Digital/EntryPermission.cs @@ -0,0 +1,15 @@ +using NodaTime; + +namespace Domain.Entities.Digital; + +public class EntryPermission +{ + public Guid EmployeeId { get; set; } + public Guid EntryId { get; set; } + public string AllowedOperations { get; set; } = null!; + public LocalDateTime? ExpiryDateTime { get; set; } + public bool IsSharedRoot { get; set; } + + public User Employee { get; set; } = null!; + public Entry Entry { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Domain/Entities/Digital/FileEntity.cs b/src/Domain/Entities/Digital/FileEntity.cs new file mode 100644 index 00000000..70c50363 --- /dev/null +++ b/src/Domain/Entities/Digital/FileEntity.cs @@ -0,0 +1,10 @@ +using Domain.Common; + +namespace Domain.Entities.Digital; + +public class FileEntity : BaseEntity +{ + public string FileType { get; set; } = null!; + public byte[] FileData { get; set; } = null!; + public string? FileExtension { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Borrow.cs b/src/Domain/Entities/Physical/Borrow.cs index a6f90f9a..bd05231a 100644 --- a/src/Domain/Entities/Physical/Borrow.cs +++ b/src/Domain/Entities/Physical/Borrow.cs @@ -1,13 +1,17 @@ using Domain.Common; +using Domain.Statuses; using NodaTime; 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 string Reason { get; set; } = null!; + public LocalDateTime ActualReturnTime { get; set; } + 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 2729d8ea..b5a491ea 100644 --- a/src/Domain/Entities/Physical/Document.cs +++ b/src/Domain/Entities/Physical/Document.cs @@ -1,13 +1,21 @@ using Domain.Common; +using Domain.Entities.Digital; +using Domain.Statuses; 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? FileId { get; set; } + public bool IsPrivate { get; set; } + + public User? Importer { get; set; } + public virtual FileEntity? File { 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 517ab146..e6b988db 100644 --- a/src/Domain/Entities/Physical/Room.cs +++ b/src/Domain/Entities/Physical/Room.cs @@ -2,15 +2,17 @@ namespace Domain.Entities.Physical; -public class Room : BaseEntity +public class Room : BaseAuditableEntity { public string Name { get; set; } = null!; public string? Description { get; set; } public Staff? Staff { get; set; } + public Guid DepartmentId { get; set; } public int Capacity { get; set; } public int NumberOfLockers { get; set; } public bool IsAvailable { get; set; } // Navigation property + public Department Department { get; set; } = null!; public ICollection Lockers { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Staff.cs b/src/Domain/Entities/Physical/Staff.cs index 1184a040..bb6f2a28 100644 --- a/src/Domain/Entities/Physical/Staff.cs +++ b/src/Domain/Entities/Physical/Staff.cs @@ -5,5 +5,5 @@ namespace Domain.Entities.Physical; public class Staff : BaseEntity { public User User { get; set; } = null!; - public Room Room { get; set; } = null!; + public Room? Room { get; set; } } \ No newline at end of file diff --git a/src/Domain/Entities/ResetPasswordToken.cs b/src/Domain/Entities/ResetPasswordToken.cs new file mode 100644 index 00000000..0ae14f1a --- /dev/null +++ b/src/Domain/Entities/ResetPasswordToken.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using NodaTime; + +namespace Domain.Entities; + +public class ResetPasswordToken +{ + [Key] public string TokenHash { get; set; } = null!; + public User User { get; set; } = null!; + public LocalDateTime ExpirationDate { get; set; } + public bool IsInvalidated { get; set; } +} \ No newline at end of file diff --git a/src/Domain/Entities/User.cs b/src/Domain/Entities/User.cs index 210a75a5..c9bb7392 100644 --- a/src/Domain/Entities/User.cs +++ b/src/Domain/Entities/User.cs @@ -1,4 +1,5 @@ using Domain.Common; +using Domain.Entities.Digital; using Domain.Entities.Physical; namespace Domain.Entities; @@ -6,8 +7,9 @@ namespace Domain.Entities; public class User : BaseAuditableEntity { public string Username { get; set; } = null!; - public string? Email { get; set; } + public string Email { get; set; } = null!; public string PasswordHash { get; set; } = null!; + public string PasswordSalt { get; set; } = null!; public string? FirstName { get; set; } public string? LastName { get; set; } public Department? Department { 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/RequestCreated.cs b/src/Domain/Events/RequestCreated.cs new file mode 100644 index 00000000..81276057 --- /dev/null +++ b/src/Domain/Events/RequestCreated.cs @@ -0,0 +1,24 @@ +using Domain.Common; + +namespace Domain.Events; + +public class RequestCreated : BaseEvent +{ + public RequestCreated(string userName, string requestType, string operation, string documentTitle, Guid requestId, string reason, Guid documentId) + { + UserName = userName; + RequestType = requestType; + Operation = operation; + DocumentTitle = documentTitle; + RequestId = requestId; + Reason = reason; + DocumentId = documentId; + } + public string UserName { get; } + public string RequestType { get; } + public string Operation { get; } + public string DocumentTitle { get; } + public string Reason { get; } + public Guid DocumentId { get; } + public Guid RequestId { get; } +} \ No newline at end of file diff --git a/src/Domain/Events/ShareEntryEvent.cs b/src/Domain/Events/ShareEntryEvent.cs new file mode 100644 index 00000000..0e2f4a2f --- /dev/null +++ b/src/Domain/Events/ShareEntryEvent.cs @@ -0,0 +1,25 @@ +using Domain.Common; + +namespace Domain.Events; + +public class ShareEntryEvent : BaseEvent +{ + public ShareEntryEvent(string entryName, string sharerName, string ownerName, string sharedUserEmail, bool isDirectory, string operation, string path) + { + EntryName = entryName; + SharerName = sharerName; + OwnerName = ownerName; + SharedUserEmail = sharedUserEmail; + IsDirectory = isDirectory; + Operation = operation; + Path = path; + } + + public string EntryName { get; } + public string SharerName { get; } + public string OwnerName { get; } + public string SharedUserEmail { get; } + public bool IsDirectory { get; } + public string Operation { get; } + public string Path { get; } +} \ 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/Events/UserCreatedEvent.cs b/src/Domain/Events/UserCreatedEvent.cs index 341553c3..eb43de5a 100644 --- a/src/Domain/Events/UserCreatedEvent.cs +++ b/src/Domain/Events/UserCreatedEvent.cs @@ -5,10 +5,12 @@ namespace Domain.Events; public class UserCreatedEvent : BaseEvent { - public UserCreatedEvent(User user) + public UserCreatedEvent(User user, string password) { User = user; + Password = password; } public User User { get; } + public string Password { get; } } \ No newline at end of file diff --git a/src/Domain/Statuses/BorrowRequestStatus.cs b/src/Domain/Statuses/BorrowRequestStatus.cs new file mode 100644 index 00000000..8ef13936 --- /dev/null +++ b/src/Domain/Statuses/BorrowRequestStatus.cs @@ -0,0 +1,14 @@ +namespace Domain.Statuses; + +public enum BorrowRequestStatus +{ + Pending, + Approved, + Rejected, + CheckedOut, + Returned, + Overdue, + Cancelled, + Lost, + NotProcessable, +} \ No newline at end of file diff --git a/src/Domain/Statuses/DocumentStatus.cs b/src/Domain/Statuses/DocumentStatus.cs new file mode 100644 index 00000000..133c1cd1 --- /dev/null +++ b/src/Domain/Statuses/DocumentStatus.cs @@ -0,0 +1,9 @@ +namespace Domain.Statuses; + +public enum DocumentStatus +{ + Issued, + Available, + Borrowed, + Lost, +} \ 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..43c221dd --- /dev/null +++ b/src/Domain/Statuses/ImportRequestStatus.cs @@ -0,0 +1,10 @@ +namespace Domain.Statuses; + +public enum ImportRequestStatus +{ + Pending, + Approved, + Rejected, + Assigned, + CheckedIn, +} \ No newline at end of file diff --git a/src/Infrastructure/ConfigureServices.cs b/src/Infrastructure/ConfigureServices.cs index bd1bede7..dfade6b9 100644 --- a/src/Infrastructure/ConfigureServices.cs +++ b/src/Infrastructure/ConfigureServices.cs @@ -1,12 +1,12 @@ -using System.Data; +using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography; -using System.Text; 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.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -20,12 +20,17 @@ public static class ConfigureServices public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) { services.AddApplicationDbContext(configuration); - services.AddScoped(); + services.AddScoped(sp => sp.GetService()!); + services.AddScoped(sp => sp.GetService()!); services.AddScoped(); + services.AddScoped(); + services.AddTransient(); + services.AddMailService(configuration); services.AddJweAuthentication(configuration); services.AddAuthorization(); + services.AddSecurityService(configuration); return services; } @@ -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 => @@ -92,4 +97,36 @@ private static IServiceCollection AddJweAuthentication(this IServiceCollection s return services; } + + private static IServiceCollection AddMailService(this IServiceCollection services, IConfiguration configuration) + { + var mailSettings = configuration.GetSection(nameof(MailSettings)).Get(); + + services.Configure(options => + { + options.ClientUrl = mailSettings!.ClientUrl; + options.Token = mailSettings!.Token; + options.SenderEmail = mailSettings!.SenderEmail; + options.SenderName = mailSettings!.SenderName; + options.TemplateUuids = mailSettings!.TemplateUuids; + }); + + services.AddTransient(); + + return services; + } + + private static IServiceCollection AddSecurityService(this IServiceCollection services, IConfiguration configuration) + { + var securitySettings = configuration.GetSection(nameof(SecuritySettings)).Get(); + + services.Configure(option => + { + option.Pepper = securitySettings!.Pepper; + }); + + services.AddTransient(); + + return services; + } } \ No newline at end of file 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..79239f56 100644 --- a/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs +++ b/src/Infrastructure/Identity/Authorization/RequiresRoleAttribute.cs @@ -1,4 +1,5 @@ -using Application.Identity; +using System.Collections.Immutable; +using System.IdentityModel.Tokens.Jwt; using Infrastructure.Persistence; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -20,17 +21,19 @@ 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)); - foreach (var claimValue in _claimValues) + if (user is null) { - if (user!.Role.Equals(claimValue)) - { - return; - } + context.Result = new ForbidResult(); + return; + } + + if (_claimValues.Any(claimValue => user!.Role.Equals(claimValue))) + { + return; } context.Result = new ForbidResult(); diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs index d1b36fac..b7b91c23 100644 --- a/src/Infrastructure/Identity/IdentityService.cs +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -3,6 +3,7 @@ using System.Security.Authentication; using System.Security.Claims; using System.Security.Cryptography; +using Application.Common.Exceptions; using Application.Common.Interfaces; using Application.Common.Models; using Application.Common.Models.Dtos; @@ -10,12 +11,12 @@ using Application.Users.Queries; using AutoMapper; using Domain.Entities; -using Infrastructure.Persistence; using Infrastructure.Shared; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using NodaTime; +using OneOf; using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames; namespace Infrastructure.Identity; @@ -24,19 +25,31 @@ public class IdentityService : IIdentityService { private readonly TokenValidationParameters _tokenValidationParameters; private readonly JweSettings _jweSettings; - private readonly ApplicationDbContext _context; + private readonly IApplicationDbContext _applicationDbContext; + private readonly IAuthDbContext _authDbContext; private readonly RSA _encryptionKey; private readonly ECDsa _signingKey; private readonly IMapper _mapper; - - public IdentityService(TokenValidationParameters tokenValidationParameters, IOptions jweSettingsOptions, ApplicationDbContext context, RSA encryptionKey, ECDsa signingKey, IMapper mapper) + private readonly SecuritySettings _securitySettings; + + public IdentityService( + TokenValidationParameters tokenValidationParameters, + IOptions jweSettingsOptions, + IApplicationDbContext applicationDbContext, + IAuthDbContext authDbContext, + RSA encryptionKey, + ECDsa signingKey, + IMapper mapper, + IOptions securitySettingsOptions) { _tokenValidationParameters = tokenValidationParameters; _jweSettings = jweSettingsOptions.Value; - _context = context; + _applicationDbContext = applicationDbContext; + _authDbContext = authDbContext; _encryptionKey = encryptionKey; _signingKey = signingKey; _mapper = mapper; + _securitySettings = securitySettingsOptions.Value; } public async Task Validate(string token, string refreshToken) @@ -48,11 +61,9 @@ 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 _context.Users.FirstOrDefaultAsync(x => + var user = await _applicationDbContext.Users.FirstOrDefaultAsync(x => x.Username.Equals(email) || x.Email!.Equals(email)); @@ -73,7 +84,7 @@ public async Task Validate(string token, string refreshToken) } var jti = validatedToken.Claims.Single(x => x.Type.Equals(JwtRegisteredClaimNames.Jti)).Value; - var storedRefreshToken = await _context.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); + var storedRefreshToken = await _authDbContext.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); if (storedRefreshToken is null) { @@ -106,12 +117,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 _context.Users.FirstOrDefaultAsync(x => + var user = await _applicationDbContext.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Username.Equals(email) || x.Email!.Equals(email)); @@ -119,20 +130,9 @@ public async Task RefreshTokenAsync(string token, string r { throw new AuthenticationException("Invalid token."); } - - var expiryDateUnix = - long.Parse(validatedToken.Claims.Single(x => x.Type.Equals(JwtRegisteredClaimNames.Exp)).Value); - - var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) - .AddSeconds(expiryDateUnix); - - if (expiryDateTimeUtc > DateTime.UtcNow) - { - throw new AuthenticationException("This token has not expired yet."); - } var jti = validatedToken.Claims.Single(x => x.Type.Equals(JwtRegisteredClaimNames.Jti)).Value; - var storedRefreshToken = await _context.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); + var storedRefreshToken = await _authDbContext.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); if (storedRefreshToken is null) { @@ -159,10 +159,9 @@ public async Task RefreshTokenAsync(string token, string r throw new AuthenticationException("This refresh token does not match this Jwt."); } - _context.RefreshTokens.Remove(storedRefreshToken); - await _context.SaveChangesAsync(); + var result = await GenerateAuthenticationResultForUserAsync(user); - return await GenerateAuthenticationResultForUserAsync(user); + return result; } private ClaimsPrincipal? GetPrincipalFromToken(string token) @@ -187,30 +186,56 @@ public async Task RefreshTokenAsync(string token, string r return principal; } - catch (SecurityTokenExpiredException ex) - { - return null; - } - catch (Exception exception) + catch { - Console.WriteLine(exception.StackTrace); return null; } } - public async Task<(AuthenticationResult, UserDto)> LoginAsync(string email, string password) + public async Task> LoginAsync(string email, string password) { - var user = _context.Users.FirstOrDefault(x => x.Email!.Equals(email)); + var user = _applicationDbContext.Users + .Include(x => x.Department) + .FirstOrDefault(x => x.Email.ToLower().Equals(email.Trim().ToLower())); - if (user is null || !user.PasswordHash.Equals(SecurityUtil.Hash(password))) + if (user is null || !user.PasswordHash.Equals(password.HashPasswordWith(user.PasswordSalt, _securitySettings.Pepper))) { throw new AuthenticationException("Username or password is invalid."); } + + if (!user.IsActivated) + { + var token = Guid.NewGuid().ToString(); + var expirationDate = LocalDateTime.FromDateTime(DateTime.Now.AddDays(1)); + var resetPasswordToken = new ResetPasswordToken() + { + User = user, + TokenHash = SecurityUtil.Hash(token), + ExpirationDate = expirationDate, + IsInvalidated = false, + }; + + var tokens = _authDbContext.ResetPasswordTokens.Where(x => x.User.Id == user.Id && !x.IsInvalidated); + + foreach (var t in tokens) + { + t.IsInvalidated = true; + } + await _authDbContext.ResetPasswordTokens.AddAsync(resetPasswordToken); + await _authDbContext.SaveChangesAsync(CancellationToken.None); + + return token; + } + + var existedRefreshTokens = _authDbContext.RefreshTokens.Where(x => x.User.Email!.Equals(user.Email)); + + _authDbContext.RefreshTokens.RemoveRange(existedRefreshTokens); + await _authDbContext.SaveChangesAsync(CancellationToken.None); return (await GenerateAuthenticationResultForUserAsync(user), _mapper.Map(user)); } - public async Task LogoutAsync(string token, string refreshToken) + public async Task LogoutAsync(string token, string refreshToken) { var validatedToken = GetPrincipalFromToken(token); @@ -221,30 +246,83 @@ public async Task LogoutAsync(string token, string refreshToken) var jti = validatedToken.Claims.Single(x => x.Type.Equals(JwtRegisteredClaimNames.Jti)).Value; var storedRefreshToken = - await _context.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); + await _authDbContext.RefreshTokens.SingleOrDefaultAsync(x => x.Token.Equals(Guid.Parse(refreshToken))); - if (storedRefreshToken is null) return true; + if (storedRefreshToken is null) return; if (!storedRefreshToken!.JwtId.Equals(jti)) { throw new AuthenticationException("This refresh token does not match this Jwt."); } - _context.RefreshTokens.Remove(storedRefreshToken); - await _context.SaveChangesAsync(); + _authDbContext.RefreshTokens.Remove(storedRefreshToken); + await _authDbContext.SaveChangesAsync(CancellationToken.None); + } + + public async Task ResetPassword(string token, string newPassword) + { + var tokenHash = SecurityUtil.Hash(token); + var resetPasswordToken = await _authDbContext.ResetPasswordTokens + .Include(x => x.User) + .FirstOrDefaultAsync(x => x.TokenHash.Equals(tokenHash)); + if (resetPasswordToken is null) + { + throw new KeyNotFoundException("Token is invalid."); + } + + if (resetPasswordToken.IsInvalidated) + { + throw new ConflictException("Token is invalid."); + } + + var user = resetPasswordToken.User; - return true; + if (user.IsActivated is false) + { + user.IsActivated = true; + } + var salt = StringUtil.RandomSalt(); + user.PasswordSalt = salt; + user.PasswordHash = newPassword.HashPasswordWith(salt, _securitySettings.Pepper); + resetPasswordToken.IsInvalidated = true; + await _applicationDbContext.SaveChangesAsync(CancellationToken.None); + await _authDbContext.SaveChangesAsync(CancellationToken.None); } private async Task GenerateAuthenticationResultForUserAsync(User user) + { + var token = CreateJweToken(user); + var utcNow = DateTime.UtcNow; + + var refreshToken = new RefreshToken() + { + JwtId = token.Id, + User = user, + CreationDateTime = LocalDateTime.FromDateTime(utcNow), + ExpiryDateTime = LocalDateTime.FromDateTime(utcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)) + }; + + await _authDbContext.RefreshTokens.AddAsync(refreshToken); + await _authDbContext.SaveChangesAsync(CancellationToken.None); + return new() + { + Token = token, + RefreshToken = _mapper.Map(refreshToken) + }; + } + + 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}; @@ -263,20 +341,6 @@ private async Task GenerateAuthenticationResultForUserAsyn var token = handler.CreateToken(tokenDescriptor); - var refreshToken = new RefreshToken() - { - JwtId = token.Id, - User = user, - CreationDateTime = LocalDateTime.FromDateTime(utcNow), - ExpiryDateTime = LocalDateTime.FromDateTime(utcNow.AddDays(_jweSettings.RefreshTokenLifetimeInDays)) - }; - - await _context.RefreshTokens.AddAsync(refreshToken); - await _context.SaveChangesAsync(); - return new() - { - Token = token, - RefreshToken = _mapper.Map(refreshToken) - }; + return token; } } \ No newline at end of file diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 911a81f6..ba75dbc4 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -7,13 +7,17 @@ + + + + @@ -21,8 +25,4 @@ - - - - diff --git a/src/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Infrastructure/Persistence/ApplicationDbContext.cs index dbae579c..15c4970e 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContext.cs @@ -1,6 +1,8 @@ using System.Reflection; using Application.Common.Interfaces; +using Application.Common.Models; using Domain.Entities; +using Domain.Entities.Digital; using Domain.Entities.Physical; using Infrastructure.Common; using MediatR; @@ -8,7 +10,7 @@ namespace Infrastructure.Persistence; -public class ApplicationDbContext : DbContext, IApplicationDbContext +public class ApplicationDbContext : DbContext, IApplicationDbContext, IAuthDbContext { private readonly IMediator _mediator; public ApplicationDbContext( @@ -25,16 +27,25 @@ 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 Files => Set(); + public DbSet Entries => Set(); + public DbSet EntryPermissions => Set(); public DbSet RefreshTokens => Set(); + public DbSet ResetPasswordTokens => Set(); + + public DbSet Logs => Set(); - protected override void OnModelCreating(ModelBuilder builder) + protected override void OnModelCreating(ModelBuilder modelBuilder) { // Scan for entity configurations using FluentAPI - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - base.OnModelCreating(builder); + base.OnModelCreating(modelBuilder); } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index a6593d43..1682d2cb 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -1,23 +1,29 @@ using Application.Helpers; using Application.Identity; +using Bogus; using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; using Infrastructure.Shared; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; using NodaTime; +using Org.BouncyCastle.Crypto.Macs; using Serilog; +using Serilog.Context; namespace Infrastructure.Persistence; -public class ApplicationDbContextSeed +public static class ApplicationDbContextSeed { public static async Task Seed(ApplicationDbContext context, IConfiguration configuration, ILogger logger) { if (!configuration.GetValue("Seed")) return; + var securitySettings = configuration.GetSection(nameof(SecuritySettings)).Get(); try { - await TrySeedAsync(context); + await TrySeedAsync(context, securitySettings!.Pepper, logger); } catch (Exception ex) { @@ -26,44 +32,535 @@ public static async Task Seed(ApplicationDbContext context, IConfiguration confi } } - private static async Task TrySeedAsync(ApplicationDbContext context) + private static async Task TrySeedAsync(ApplicationDbContext context, string pepper, ILogger logger) { - var department = new Department() + await context.SeedAdminDepartment(pepper); + await context.SeedItDepartment(pepper); + await context.SeedHrDepartment(pepper); + await context.SeedAccountingDepartment(pepper); + + await context.SaveChangesAsync(); + } + + private static async Task SeedAdminDepartment(this ApplicationDbContext context, string pepper) + { + var salt = StringUtil.RandomSalt(); + + var adminDepartment = new Department() { Name = "Admin" }; - - // Default users + var admin = new User { - Username = "admin", - Email = "admin@profile.dev", - PasswordHash = SecurityUtil.Hash("admin"), + Username = "admin", + Email = "admin@profile.dev", + PasswordHash = "admin".HashPasswordWith(salt, pepper), + PasswordSalt = salt, IsActive = true, IsActivated = true, Created = LocalDateTime.FromDateTime(DateTime.UtcNow), Role = IdentityData.Roles.Admin, }; + + await context.TryAddDepartmentWith(adminDepartment, + new List() + { + admin + }, + Enumerable.Empty()); + } + + private static async Task SeedItDepartment(this ApplicationDbContext context, string pepper) + { + var itDepartment = new Department() + { + Name = "IT" + }; + + var employee1 = CreateEmployee("employee", "employee@profile.dev", "employee", pepper); + var employee2 = CreateRandomEmployee("johndoe", pepper); + var employee3 = CreateRandomEmployee("johnnysins", pepper); + + var staffUser = CreateStaff("staff", "staff@profile.dev", "staff", pepper); + var staffUser2 = CreateStaff("staff2", "staff2@profile.dev", "staff", pepper); + + await context.TryAddDepartmentWith(itDepartment, + new List() + { + employee1, + employee2, + employee3, + }, + new List() + { + staffUser, + staffUser2, + }); + + var d1 = CreateNDocument(employee1, itDepartment, 5); + d1.AddRange(CreateNDocument(employee2, itDepartment, 4)); + var folder1 = CreateFolder("Short-term contracts 1", "Short term contracts", 20,d1); + var d2 = CreateNDocument(employee3, itDepartment, 8); + d2.AddRange(CreateNDocument(employee2, itDepartment, 7)); + var folder2 = CreateFolder("Short-term contracts 2", "Short-term contracts 2", 15, + CreateNDocument(employee2, itDepartment, 7)); + var d3 = CreateNDocument(employee1, itDepartment, 3); + d3.AddRange(CreateNDocument(employee2, itDepartment, 7)); + d3.AddRange(CreateNDocument(employee3, itDepartment, 4)); + var folder3 = CreateFolder("Long-term contracts 1", "Long-term contracts 1", 30, d3); + var d4 = CreateNDocument(employee1, itDepartment, 2); + d4.AddRange(CreateNDocument(employee2, itDepartment, 9)); + d4.AddRange(CreateNDocument(employee3, itDepartment, 4)); + var folder4 = CreateFolder("Long-term contracts 2", "Long-term contracts 2", 20, d4); + var d5 = CreateNDocument(employee1, itDepartment, 5); + d5.AddRange(CreateNDocument(employee2, itDepartment, 1)); + d5.AddRange(CreateNDocument(employee3, itDepartment, 8)); + var folder5 = CreateFolder("Long-term contracts 3", "Long-term contracts 3", 20, d5); + var d6 = CreateNDocument(employee1, itDepartment, 5); + d6.AddRange(CreateNDocument(employee2, itDepartment, 5)); + d6.AddRange(CreateNDocument(employee3, itDepartment, 5)); + var folder6 = CreateFolder("SRS", "Software requirement specification", 20, d6); + var d7 = CreateNDocument(employee1, itDepartment, 1); + d7.AddRange(CreateNDocument(employee2, itDepartment, 1)); + d7.AddRange(CreateNDocument(employee3, itDepartment, 12)); + var folder7 = CreateFolder("SDS", "Software design specification", 20, d7); + var d8 = CreateNDocument(employee1, itDepartment, 7); + d8.AddRange(CreateNDocument(employee2, itDepartment, 5)); + d8.AddRange(CreateNDocument(employee3, itDepartment, 1)); + var folder8 = CreateFolder("Private invoices", "Private invoices", 20, d8); + var d9 = CreateNDocument(employee1, itDepartment, 5); + d9.AddRange(CreateNDocument(employee2, itDepartment, 8)); + d9.AddRange(CreateNDocument(employee3, itDepartment, 1)); + var folder9 = CreateFolder("Public invoices", "Public invoices", 20, d9); + + var locker1 = CreateLocker("Contracts", "Contracts", 20, new List() + { + folder1, + folder2, + folder3, + folder4, + folder5, + }); + var locker2 = CreateLocker("Specifications", "All types of specifications", 20, new List() + { + folder6, + folder7, + }); + var locker3 = CreateLocker("Invoices", "Invoices related to products", 20, new List() + { + folder8, + folder9, + }); + + var locker4 = CreateLocker("Papers", "All research papers related", 20, new List() + { + + }); + var locker5 = CreateLocker("Reports", "Annually, Monthly, Weekly reports", 20, new List()); + await context.TryAddRoom("IT Storage", "Storage for IT Department", 40, itDepartment, staffUser, new List() + { + locker1, + locker2, + locker3, + }); + await context.TryAddRoom("IT Storage 2", "Storage 2 for IT Department", 30, itDepartment, staffUser2, new List() + { + locker4, + locker5, + }); + } + + private static async Task SeedHrDepartment(this ApplicationDbContext context, string pepper) + { + var hrDepartment = new Department + { + Name = "Human Resource" + }; + + var employee1 = CreateRandomEmployee("cuonglt2", pepper); + var employee2 = CreateRandomEmployee("thanht", pepper); + var employee3 = CreateRandomEmployee("chaum", pepper); + var employee4 = CreateRandomEmployee("tuantt", pepper); + + var staff = CreateStaff("maint", "maint@profile.dev", "employee", pepper); + + await context.TryAddDepartmentWith(hrDepartment, + new List() + { + employee1, + employee2, + employee3, + employee4, + }, + new List() + { + staff, + }); + + var d1 = CreateNDocument(employee1, hrDepartment, 5); + d1.AddRange(CreateNDocument(employee2, hrDepartment, 4)); + var folder1 = CreateFolder("Short-term contracts 1", "Short term contracts", 20,d1); + var d2 = CreateNDocument(employee3, hrDepartment, 8); + d2.AddRange(CreateNDocument(employee2, hrDepartment, 7)); + var folder2 = CreateFolder("Short-term contracts 2", "Short-term contracts 2", 15, + CreateNDocument(employee2, hrDepartment, 7)); + var d3 = CreateNDocument(employee1, hrDepartment, 3); + d3.AddRange(CreateNDocument(employee2, hrDepartment, 7)); + d3.AddRange(CreateNDocument(employee3, hrDepartment, 4)); + var folder3 = CreateFolder("Long-term contracts 1", "Long-term contracts 1", 30, d3); + var d4 = CreateNDocument(employee1, hrDepartment, 2); + d4.AddRange(CreateNDocument(employee2, hrDepartment, 9)); + d4.AddRange(CreateNDocument(employee3, hrDepartment, 4)); + var folder4 = CreateFolder("Long-term contracts 2", "Long-term contracts 2", 20, d4); + var d5 = CreateNDocument(employee1, hrDepartment, 5); + d5.AddRange(CreateNDocument(employee2, hrDepartment, 1)); + d5.AddRange(CreateNDocument(employee3, hrDepartment, 8)); + var folder5 = CreateFolder("Long-term contracts 3", "Long-term contracts 3", 20, d5); + var d6 = CreateNDocument(employee1, hrDepartment, 5); + d6.AddRange(CreateNDocument(employee2, hrDepartment, 5)); + d6.AddRange(CreateNDocument(employee3, hrDepartment, 5)); + var folder6 = CreateFolder("SRS", "Software requirement specification", 20, d6); + var d7 = CreateNDocument(employee1, hrDepartment, 1); + d7.AddRange(CreateNDocument(employee2, hrDepartment, 1)); + d7.AddRange(CreateNDocument(employee3, hrDepartment, 12)); + var folder7 = CreateFolder("SDS", "Software design specification", 20, d7); + var d8 = CreateNDocument(employee1, hrDepartment, 7); + d8.AddRange(CreateNDocument(employee2, hrDepartment, 5)); + d8.AddRange(CreateNDocument(employee3, hrDepartment, 1)); + var folder8 = CreateFolder("Private invoices", "Private invoices", 20, d8); + var d9 = CreateNDocument(employee1, hrDepartment, 5); + d9.AddRange(CreateNDocument(employee2, hrDepartment, 8)); + d9.AddRange(CreateNDocument(employee3, hrDepartment, 1)); + var folder9 = CreateFolder("Public invoices", "Public invoices", 20, d9); + + var locker1 = CreateLocker("Contracts", "Contracts", 20, new List() + { + folder1, + folder2, + folder3, + folder4, + folder5, + }); + var locker2 = CreateLocker("Specifications", "All types of specifications", 20, new List() + { + folder6, + folder7, + }); + var locker3 = CreateLocker("Invoices", "Invoices related to products", 20, new List() + { + folder8, + folder9, + }); + + var locker4 = CreateLocker("Papers", "All research papers related", 20, new List() + { + + }); + var locker5 = CreateLocker("Reports", "Annually, Monthly, Weekly reports", 20, new List()); + await context.TryAddRoom("HR Storage", "Storage for IT Department", 40, hrDepartment, staff, new List() + { + locker1, + locker2, + locker3, + locker4, + locker5, + }); + } + + private static async Task SeedAccountingDepartment(this ApplicationDbContext context, string pepper) + { + var accountingDepartment = new Department + { + Name = "Accounting" + }; + + var employee1 = CreateRandomEmployee("peterparker", pepper); + var employee2 = CreateRandomEmployee("maryjane", pepper); + var employee3 = CreateRandomEmployee("gwenstacy", pepper); + var employee4 = CreateRandomEmployee("miles", pepper); + + var staff = CreateStaff("eddie", "eddie@profile.dev", "employee", pepper); + await context.TryAddDepartmentWith(accountingDepartment, + new List() + { + employee1, + employee2, + employee3, + employee4, + }, + new List() + { + staff, + }); + + + var d1 = CreateNDocument(employee1, accountingDepartment, 5); + d1.AddRange(CreateNDocument(employee2, accountingDepartment, 4)); + var folder1 = CreateFolder("Short-term contracts 1", "Short term contracts", 20,d1); + var d2 = CreateNDocument(employee3, accountingDepartment, 8); + d2.AddRange(CreateNDocument(employee2, accountingDepartment, 7)); + var folder2 = CreateFolder("Short-term contracts 2", "Short-term contracts 2", 15, + CreateNDocument(employee2, accountingDepartment, 7)); + var d3 = CreateNDocument(employee1, accountingDepartment, 3); + d3.AddRange(CreateNDocument(employee2, accountingDepartment, 7)); + d3.AddRange(CreateNDocument(employee3, accountingDepartment, 4)); + var folder3 = CreateFolder("Long-term contracts 1", "Long-term contracts 1", 30, d3); + var d4 = CreateNDocument(employee1, accountingDepartment, 2); + d4.AddRange(CreateNDocument(employee2, accountingDepartment, 9)); + d4.AddRange(CreateNDocument(employee3, accountingDepartment, 4)); + var folder4 = CreateFolder("Long-term contracts 2", "Long-term contracts 2", 20, d4); + var d5 = CreateNDocument(employee1, accountingDepartment, 5); + d5.AddRange(CreateNDocument(employee2, accountingDepartment, 1)); + d5.AddRange(CreateNDocument(employee3, accountingDepartment, 8)); + var folder5 = CreateFolder("Long-term contracts 3", "Long-term contracts 3", 20, d5); + var d6 = CreateNDocument(employee1, accountingDepartment, 5); + d6.AddRange(CreateNDocument(employee2, accountingDepartment, 5)); + d6.AddRange(CreateNDocument(employee3, accountingDepartment, 5)); + var folder6 = CreateFolder("SRS", "Software requirement specification", 20, d6); + var d7 = CreateNDocument(employee1, accountingDepartment, 1); + d7.AddRange(CreateNDocument(employee2, accountingDepartment, 1)); + d7.AddRange(CreateNDocument(employee3, accountingDepartment, 12)); + var folder7 = CreateFolder("SDS", "Software design specification", 20, d7); + var d8 = CreateNDocument(employee1, accountingDepartment, 7); + d8.AddRange(CreateNDocument(employee2, accountingDepartment, 5)); + d8.AddRange(CreateNDocument(employee3, accountingDepartment, 1)); + var folder8 = CreateFolder("Private invoices", "Private invoices", 20, d8); + var d9 = CreateNDocument(employee1, accountingDepartment, 5); + d9.AddRange(CreateNDocument(employee2, accountingDepartment, 8)); + d9.AddRange(CreateNDocument(employee3, accountingDepartment, 1)); + var folder9 = CreateFolder("Public invoices", "Public invoices", 20, d9); + + var locker1 = CreateLocker("Contracts", "Contracts", 20, new List() + { + folder1, + folder2, + folder3, + folder4, + folder5, + }); + var locker2 = CreateLocker("Specifications", "All types of specifications", 20, new List() + { + folder6, + folder7, + }); + var locker3 = CreateLocker("Invoices", "Invoices related to products", 20, new List() + { + folder8, + folder9, + }); + + var locker4 = CreateLocker("Papers", "All research papers related", 20, new List() + { + + }); + var locker5 = CreateLocker("Reports", "Annually, Monthly, Weekly reports", 20, new List()); + await context.TryAddRoom("Accounting Storage", "Storage for Accounting Department", 40, accountingDepartment, staff, new List() + { + locker1, + locker2, + locker3, + locker4, + locker5, + }); + } + + private static User CreateRandomEmployee(string username, string pepper) + { + var salt = StringUtil.RandomSalt(); + return new User() + { + Username = username, + Email = new Faker().Person.Email, + FirstName = new Faker().Person.FirstName, + LastName = new Faker().Person.LastName, + PasswordHash = "employee".HashPasswordWith(salt, pepper), + PasswordSalt = salt, + IsActive = true, + IsActivated = true, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Role = IdentityData.Roles.Employee, + }; + } + + private static User CreateEmployee(string username, string email, string password, string pepper) + { + var salt = StringUtil.RandomSalt(); + return new User() + { + Username = username, + Email = email, + FirstName = new Faker().Person.FirstName, + LastName = new Faker().Person.LastName, + PasswordHash = password.HashPasswordWith(salt, pepper), + PasswordSalt = salt, + IsActive = true, + IsActivated = true, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Role = IdentityData.Roles.Employee, + }; + } + + private static User CreateStaff(string username, string email, string password, string pepper) + { + var salt = StringUtil.RandomSalt(); + return new User() + { + Username = username, + Email = email, + FirstName = new Faker().Person.FirstName, + LastName = new Faker().Person.LastName, + PasswordHash = password.HashPasswordWith(salt, pepper), + PasswordSalt = salt, + IsActive = true, + IsActivated = true, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Role = IdentityData.Roles.Staff, + }; + } + + private static async Task TryAddDepartmentWith(this ApplicationDbContext context, Department department, + IEnumerable users, IEnumerable staffs) + { if (context.Departments.All(u => u.Name != department.Name)) { await context.Departments.AddAsync(department); - if (context.Users.All(u => u.Username != admin.Username)) - { - admin.Department = department; - await context.Users.AddAsync(admin); - } + await context.AddUsers(department, users); + await context.AddStaffs(department, staffs); } else { var departmentEntity = context.Departments.Single(x => x.Name.Equals(department.Name)); - if (context.Users.All(u => u.Username != admin.Username)) + await context.AddUsers(departmentEntity, users); + await context.AddStaffs(departmentEntity, staffs); + } + } + + private static async Task TryAddRoom(this ApplicationDbContext context, string name, + string description, int capacity, Department department, User user, ICollection lockers) + { + var staff = await context.Staffs.FirstOrDefaultAsync(x => x.User.Id == user.Id); + if (context.Rooms.All(x => x.Name != name)) + { + var room = new Room { - admin.Department = departmentEntity; - await context.Users.AddAsync(admin); - } + Name = name, + Department = department, + DepartmentId = department.Id, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Staff = staff, + Lockers = lockers, + Capacity = capacity, + NumberOfLockers = lockers.Count, + IsAvailable = true, + Description = description, + }; + await context.Rooms.AddAsync(room); } + } - await context.SaveChangesAsync(); + private static Locker CreateLocker(string name, string description, int capacity, ICollection folders) + { + return new Locker + { + Name = name, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Capacity = capacity, + IsAvailable = true, + Description = description, + Folders = folders, + NumberOfFolders = folders.Count, + }; + } + + private static Folder CreateFolder(string name, string description, int capacity, ICollection documents) + { + return new Folder + { + Name = name, + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Capacity = capacity, + IsAvailable = true, + Description = description, + Documents = documents, + NumberOfDocuments = documents.Count, + }; + } + + private static Document CreateDocument(User user, Department department) + { + return new Document + { + Title = new Faker().Name.JobArea(), + Created = LocalDateTime.FromDateTime(DateTime.UtcNow), + Description = new Faker().Commerce.ProductDescription(), + DocumentType = new Faker().Commerce.Categories(1)[0], + Department = department, + Importer = user, + ImporterId = user.Id, + IsPrivate = new Faker().Random.Bool(), + Status = Random(), + CreatedBy = user.Id, + }; + } + + private static DocumentStatus Random() + { + var r = new Random().Next(0, 4); + + return r switch + { + 0 => DocumentStatus.Available, + 1 => DocumentStatus.Borrowed, + 2 => DocumentStatus.Issued, + 3 => DocumentStatus.Lost, + _ => DocumentStatus.Available + }; + } + + private static List CreateNDocument(User user, Department department, int n) + { + var list = new List(); + for (var i = 0; i < n; i++) + { + list.Add(CreateDocument(user,department)); + } + + return list; + } + + private static async Task AddUsers(this ApplicationDbContext context, Department department, + IEnumerable users) + { + foreach (var user in users) + { + if (!context.Users.All(u => u.Username != user.Username)) continue; + user.Department = department; + await context.Users.AddAsync(user); + } + } + + private static async Task AddStaffs(this ApplicationDbContext context, Department department, + IEnumerable staffs) + { + foreach (var staff in staffs) + { + if (context.Users.All(u => u.Username != staff.Username)) + { + staff.Department = department; + await context.Users.AddAsync(staff); + } + + var staffEntity = new Staff + { + User = staff, + Room = null, + }; + if (context.Staffs.All(s => s.User.Username != staff.Username)) + { + await context.Staffs.AddAsync(staffEntity); + } + } } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs index 88bd292f..7b855437 100644 --- a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs @@ -28,7 +28,10 @@ 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) .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/DepartmentConfiguration.cs b/src/Infrastructure/Persistence/Configurations/DepartmentConfiguration.cs index 8af33bea..1c7f00ca 100644 --- a/src/Infrastructure/Persistence/Configurations/DepartmentConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/DepartmentConfiguration.cs @@ -1,4 +1,5 @@ using Domain.Entities; +using Domain.Entities.Physical; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.ValueGeneration; diff --git a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs index 79e66f71..59aa2c77 100644 --- a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs @@ -36,7 +36,18 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(x => x.Importer) .WithMany() - .HasForeignKey("ImporterId") + .HasForeignKey(x => x.ImporterId) .IsRequired(false); + + builder.Property(x => x.Status) + .IsRequired(); + + builder.HasOne(x => x.File) + .WithOne() + .HasForeignKey(x => x.FileId) + .IsRequired(false); + + builder.Property(x => x.IsPrivate) + .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/EntryConfiguration.cs b/src/Infrastructure/Persistence/Configurations/EntryConfiguration.cs new file mode 100644 index 00000000..a4c0a9c4 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/EntryConfiguration.cs @@ -0,0 +1,40 @@ +using Domain.Entities.Digital; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class EntryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .ValueGeneratedOnAdd(); + + builder.Property(x => x.Name) + .HasMaxLength(256) + .IsRequired(); + + builder.Property(x => x.Path) + .IsRequired(); + + builder.HasOne(x => x.File) + .WithOne() + .HasForeignKey(x => x.FileId) + .IsRequired(false); + + builder.HasOne(x => x.Uploader) + .WithMany() + .HasForeignKey(x => x.CreatedBy) + .IsRequired(); + + builder.HasOne(x => x.Owner) + .WithMany() + .HasForeignKey(x => x.OwnerId) + .IsRequired(); + + builder.Property(x => x.SizeInBytes) + .IsRequired(false); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/EntryPermissionConfiguration.cs b/src/Infrastructure/Persistence/Configurations/EntryPermissionConfiguration.cs new file mode 100644 index 00000000..6c952d5b --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/EntryPermissionConfiguration.cs @@ -0,0 +1,22 @@ +using Domain.Entities.Digital; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class EntryPermissionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => new { x.EntryId, x.EmployeeId }); + + builder.Property(x => x.AllowedOperations) + .IsRequired(); + + builder.Property(x => x.IsSharedRoot) + .IsRequired(); + + builder.Property(x => x.ExpiryDateTime) + .IsRequired(false); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/FileConfiguration.cs b/src/Infrastructure/Persistence/Configurations/FileConfiguration.cs new file mode 100644 index 00000000..b0b9dae5 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/FileConfiguration.cs @@ -0,0 +1,25 @@ +using Domain.Entities.Digital; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class FileConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .ValueGeneratedOnAdd(); + + builder.Property(x => x.FileType) + .HasMaxLength(256) + .IsRequired(); + + builder.Property(x => x.FileData) + .IsRequired(); + + builder.Property(x => x.FileExtension) + .IsRequired(false); + } +} \ 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/ResetPasswordTokenConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ResetPasswordTokenConfiguration.cs new file mode 100644 index 00000000..36b168e6 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/ResetPasswordTokenConfiguration.cs @@ -0,0 +1,24 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations; + +public class ResetPasswordTokenConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.TokenHash); + + builder.Property(x => x.ExpirationDate) + .IsRequired(); + + builder.Property(x => x.IsInvalidated) + .HasDefaultValue(false); + + builder.HasOne(x => x.User) + .WithMany() + .HasForeignKey("UserId") + .IsRequired(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs b/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs index 636a330b..98b4fea3 100644 --- a/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/RoomConfiguration.cs @@ -26,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/StaffConfiguration.cs b/src/Infrastructure/Persistence/Configurations/StaffConfiguration.cs index 316bf414..b2881567 100644 --- a/src/Infrastructure/Persistence/Configurations/StaffConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/StaffConfiguration.cs @@ -22,6 +22,6 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(x => x.Room) .WithOne(x => x.Staff) .HasForeignKey("RoomId") - .IsRequired(); + .IsRequired(false); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/UserConfiguration.cs b/src/Infrastructure/Persistence/Configurations/UserConfiguration.cs index a12b422f..3240464d 100644 --- a/src/Infrastructure/Persistence/Configurations/UserConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/UserConfiguration.cs @@ -19,11 +19,15 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Email) .HasMaxLength(320) - .IsRequired(false); + .IsRequired(); builder.Property(x => x.PasswordHash) .HasMaxLength(64) .IsRequired(); + + builder.Property(x => x.PasswordSalt) + .HasMaxLength(32) + .IsRequired(); builder.Property(x => x.FirstName) .HasMaxLength(50) diff --git a/src/Infrastructure/Persistence/Migrations/20230524080300_Add_Refresh_Token.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000007_Add_Refresh_Token.Designer.cs similarity index 100% rename from src/Infrastructure/Persistence/Migrations/20230524080300_Add_Refresh_Token.Designer.cs rename to src/Infrastructure/Persistence/Migrations/00000000000007_Add_Refresh_Token.Designer.cs diff --git a/src/Infrastructure/Persistence/Migrations/20230524080300_Add_Refresh_Token.cs b/src/Infrastructure/Persistence/Migrations/00000000000007_Add_Refresh_Token.cs similarity index 100% rename from src/Infrastructure/Persistence/Migrations/20230524080300_Add_Refresh_Token.cs rename to src/Infrastructure/Persistence/Migrations/00000000000007_Add_Refresh_Token.cs diff --git a/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.Designer.cs new file mode 100644 index 00000000..1ff0d731 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.Designer.cs @@ -0,0 +1,475 @@ +// +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("20230527234317_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship")] + partial class UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship + { + /// + 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.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + 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.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("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("Domain.Entities.Department", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Department") + .HasForeignKey("Domain.Entities.Department", "RoomId"); + + b.Navigation("Room"); + }); + + 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.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + 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.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.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + 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("Department"); + + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.cs b/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.cs new file mode 100644 index 00000000..6bd9968b --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000008_UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship.cs @@ -0,0 +1,123 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class UpdateRoomAndStaffRelationshipAndRoomAndDepartmentRelationship : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Staffs_Rooms_RoomId", + table: "Staffs"); + + migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "character varying(320)", + maxLength: 320, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "character varying(320)", + oldMaxLength: 320, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RoomId", + table: "Staffs", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "DepartmentId", + table: "Rooms", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "RoomId", + table: "Departments", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Departments_RoomId", + table: "Departments", + column: "RoomId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Staffs_Rooms_RoomId", + table: "Staffs", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments"); + + migrationBuilder.DropForeignKey( + name: "FK_Staffs_Rooms_RoomId", + table: "Staffs"); + + migrationBuilder.DropIndex( + name: "IX_Departments_RoomId", + table: "Departments"); + + migrationBuilder.DropColumn( + name: "DepartmentId", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "RoomId", + table: "Departments"); + + migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "character varying(320)", + maxLength: 320, + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(320)", + oldMaxLength: 320); + + migrationBuilder.AlterColumn( + name: "RoomId", + table: "Staffs", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_Staffs_Rooms_RoomId", + table: "Staffs", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs new file mode 100644 index 00000000..39077769 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs @@ -0,0 +1,486 @@ +// +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("20230603132557_UpdateBorrow")] + partial class UpdateBorrow + { + /// + 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.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("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .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("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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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.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("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("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.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + 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.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + 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/00000000000010_UpdateBorrow.cs b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.cs new file mode 100644 index 00000000..1cf4d397 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.cs @@ -0,0 +1,124 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class UpdateBorrow : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments"); + + migrationBuilder.DropIndex( + name: "IX_Departments_RoomId", + table: "Departments"); + + migrationBuilder.DropColumn( + name: "RoomId", + table: "Departments"); + + migrationBuilder.AlterColumn( + name: "DepartmentId", + table: "Rooms", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Status", + table: "Documents", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ActualReturnTime", + table: "Borrows", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "Status", + table: "Borrows", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms", + column: "DepartmentId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Rooms_Departments_DepartmentId", + table: "Rooms", + column: "DepartmentId", + principalTable: "Departments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rooms_Departments_DepartmentId", + table: "Rooms"); + + migrationBuilder.DropIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "Status", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "ActualReturnTime", + table: "Borrows"); + + migrationBuilder.DropColumn( + name: "Status", + table: "Borrows"); + + migrationBuilder.AlterColumn( + name: "DepartmentId", + table: "Rooms", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "RoomId", + table: "Departments", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Departments_RoomId", + table: "Departments", + column: "RoomId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000011_AddUserGroup.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000011_AddUserGroup.Designer.cs new file mode 100644 index 00000000..132fc48c --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000011_AddUserGroup.Designer.cs @@ -0,0 +1,533 @@ +// +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("20230606074719_AddUserGroup")] + partial class AddUserGroup + { + /// + 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.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.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("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .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("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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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.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("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.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.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + 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.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/00000000000011_AddUserGroup.cs b/src/Infrastructure/Persistence/Migrations/00000000000011_AddUserGroup.cs new file mode 100644 index 00000000..db90a1c1 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000011_AddUserGroup.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddUserGroup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserGroups", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserGroups", x => x.Id); + table.UniqueConstraint("AK_UserGroups_Name", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "Memberships", + columns: table => new + { + UserId = table.Column(type: "uuid", nullable: false), + UserGroupId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Memberships", x => new { x.UserId, x.UserGroupId }); + table.ForeignKey( + name: "FK_Memberships_UserGroups_UserGroupId", + column: x => x.UserGroupId, + principalTable: "UserGroups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Memberships_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Memberships_UserGroupId", + table: "Memberships", + column: "UserGroupId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Memberships"); + + migrationBuilder.DropTable( + name: "UserGroups"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000012_AddDigitalFileAndEntry.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000012_AddDigitalFileAndEntry.Designer.cs new file mode 100644 index 00000000..63d37967 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000012_AddDigitalFileAndEntry.Designer.cs @@ -0,0 +1,600 @@ +// +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("20230606092912_AddDigitalFileAndEntry")] + partial class AddDigitalFileAndEntry + { + /// + 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.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("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + 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("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("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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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.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("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.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.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/00000000000012_AddDigitalFileAndEntry.cs b/src/Infrastructure/Persistence/Migrations/00000000000012_AddDigitalFileAndEntry.cs new file mode 100644 index 00000000..ecd2d66a --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000012_AddDigitalFileAndEntry.cs @@ -0,0 +1,94 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddDigitalFileAndEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EntryId", + table: "Documents", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "Files", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + FileType = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + FileData = table.Column(type: "bytea", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Files", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Entries", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Path = table.Column(type: "text", nullable: false), + FileId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Entries", x => x.Id); + table.ForeignKey( + name: "FK_Entries_Files_FileId", + column: x => x.FileId, + principalTable: "Files", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Documents_EntryId", + table: "Documents", + column: "EntryId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Entries_FileId", + table: "Entries", + column: "FileId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_Entries_EntryId", + table: "Documents", + column: "EntryId", + principalTable: "Entries", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Documents_Entries_EntryId", + table: "Documents"); + + migrationBuilder.DropTable( + name: "Entries"); + + migrationBuilder.DropTable( + name: "Files"); + + migrationBuilder.DropIndex( + name: "IX_Documents_EntryId", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "EntryId", + table: "Documents"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000013_AddPasswordSaltFieldToUsers.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000013_AddPasswordSaltFieldToUsers.Designer.cs new file mode 100644 index 00000000..d702cff5 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000013_AddPasswordSaltFieldToUsers.Designer.cs @@ -0,0 +1,491 @@ +// +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("20230608044119_AddPasswordSaltFieldToUsers")] + partial class AddPasswordSaltFieldToUsers + { + /// + 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.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("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .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("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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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.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("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.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + 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.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + 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/00000000000013_AddPasswordSaltFieldToUsers.cs b/src/Infrastructure/Persistence/Migrations/00000000000013_AddPasswordSaltFieldToUsers.cs new file mode 100644 index 00000000..6b4e0f69 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000013_AddPasswordSaltFieldToUsers.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddPasswordSaltFieldToUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PasswordSalt", + table: "Users", + type: "character varying(32)", + maxLength: 32, + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PasswordSalt", + table: "Users"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230607093007_AddResetPasswordToken.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230607093007_AddResetPasswordToken.Designer.cs new file mode 100644 index 00000000..22ae64be --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230607093007_AddResetPasswordToken.Designer.cs @@ -0,0 +1,520 @@ +// +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("20230607093007_AddResetPasswordToken")] + partial class AddResetPasswordToken + { + /// + 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.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("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .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("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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + 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("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("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.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + 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("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/20230607093007_AddResetPasswordToken.cs b/src/Infrastructure/Persistence/Migrations/20230607093007_AddResetPasswordToken.cs new file mode 100644 index 00000000..3a34e1da --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230607093007_AddResetPasswordToken.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddResetPasswordToken : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ResetPasswordTokens", + columns: table => new + { + TokenHash = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + ExpirationDate = table.Column(type: "timestamp without time zone", nullable: false), + IsInvalidated = table.Column(type: "boolean", nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ResetPasswordTokens", x => x.TokenHash); + table.ForeignKey( + name: "FK_ResetPasswordTokens_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ResetPasswordTokens_UserId", + table: "ResetPasswordTokens", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ResetPasswordTokens"); + } + } +} 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/20230624100226_TrueLogging.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230624100226_TrueLogging.Designer.cs new file mode 100644 index 00000000..9929cf86 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230624100226_TrueLogging.Designer.cs @@ -0,0 +1,1105 @@ +// +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("20230624100226_TrueLogging")] + partial class TrueLogging + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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/20230624100226_TrueLogging.cs b/src/Infrastructure/Persistence/Migrations/20230624100226_TrueLogging.cs new file mode 100644 index 00000000..0dd3f20e --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230624100226_TrueLogging.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class TrueLogging : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Logs", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Template = table.Column(type: "text", nullable: true), + Message = table.Column(type: "text", nullable: true), + Level = table.Column(type: "text", nullable: true), + Time = table.Column(type: "timestamp with time zone", nullable: true), + Event = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "uuid", nullable: true), + ObjectId = table.Column(type: "uuid", nullable: true), + ObjectType = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Logs", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Logs"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230629012929_FileAndEntry.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230629012929_FileAndEntry.Designer.cs new file mode 100644 index 00000000..1ace2e14 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230629012929_FileAndEntry.Designer.cs @@ -0,0 +1,1107 @@ +// +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("20230629012929_FileAndEntry")] + partial class FileAndEntry + { + /// + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + 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.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + 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/20230629012929_FileAndEntry.cs b/src/Infrastructure/Persistence/Migrations/20230629012929_FileAndEntry.cs new file mode 100644 index 00000000..5be77408 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230629012929_FileAndEntry.cs @@ -0,0 +1,125 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class FileAndEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FileExtension", + table: "Files", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "Created", + table: "Entries", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "Entries", + type: "timestamp without time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "Entries", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "OwnerId", + table: "Entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_Entries_CreatedBy", + table: "Entries", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_Entries_OwnerId", + table: "Entries", + column: "OwnerId"); + + migrationBuilder.AddForeignKey( + name: "FK_Entries_Users_CreatedBy", + table: "Entries", + column: "CreatedBy", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Entries_Users_OwnerId", + table: "Entries", + column: "OwnerId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Entries_Users_CreatedBy", + table: "Entries"); + + migrationBuilder.DropForeignKey( + name: "FK_Entries_Users_OwnerId", + table: "Entries"); + + migrationBuilder.DropIndex( + name: "IX_Entries_CreatedBy", + table: "Entries"); + + migrationBuilder.DropIndex( + name: "IX_Entries_OwnerId", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "FileExtension", + table: "Files"); + + migrationBuilder.DropColumn( + name: "Created", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "OwnerId", + table: "Entries"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230701070615_DocumentHasFileInsteadOfEntry.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230701070615_DocumentHasFileInsteadOfEntry.Designer.cs new file mode 100644 index 00000000..d39293a4 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701070615_DocumentHasFileInsteadOfEntry.Designer.cs @@ -0,0 +1,1097 @@ +// +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("20230701070615_DocumentHasFileInsteadOfEntry")] + partial class DocumentHasFileInsteadOfEntry + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230701070615_DocumentHasFileInsteadOfEntry.cs b/src/Infrastructure/Persistence/Migrations/20230701070615_DocumentHasFileInsteadOfEntry.cs new file mode 100644 index 00000000..89ab2d7f --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701070615_DocumentHasFileInsteadOfEntry.cs @@ -0,0 +1,109 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class DocumentHasFileInsteadOfEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Documents_Entries_EntryId", + table: "Documents"); + + migrationBuilder.DropTable( + name: "Memberships"); + + migrationBuilder.DropTable( + name: "UserGroups"); + + migrationBuilder.RenameColumn( + name: "EntryId", + table: "Documents", + newName: "FileId"); + + migrationBuilder.RenameIndex( + name: "IX_Documents_EntryId", + table: "Documents", + newName: "IX_Documents_FileId"); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_Files_FileId", + table: "Documents", + column: "FileId", + principalTable: "Files", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Documents_Files_FileId", + table: "Documents"); + + migrationBuilder.RenameColumn( + name: "FileId", + table: "Documents", + newName: "EntryId"); + + migrationBuilder.RenameIndex( + name: "IX_Documents_FileId", + table: "Documents", + newName: "IX_Documents_EntryId"); + + migrationBuilder.CreateTable( + name: "UserGroups", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserGroups", x => x.Id); + table.UniqueConstraint("AK_UserGroups_Name", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "Memberships", + columns: table => new + { + UserId = table.Column(type: "uuid", nullable: false), + UserGroupId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Memberships", x => new { x.UserId, x.UserGroupId }); + table.ForeignKey( + name: "FK_Memberships_UserGroups_UserGroupId", + column: x => x.UserGroupId, + principalTable: "UserGroups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Memberships_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Memberships_UserGroupId", + table: "Memberships", + column: "UserGroupId"); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_Entries_EntryId", + table: "Documents", + column: "EntryId", + principalTable: "Entries", + principalColumn: "Id"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230701144244_EntryPermissionEntity.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230701144244_EntryPermissionEntity.Designer.cs new file mode 100644 index 00000000..a9e42a6c --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701144244_EntryPermissionEntity.Designer.cs @@ -0,0 +1,1138 @@ +// +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("20230701144244_EntryPermissionEntity")] + partial class EntryPermissionEntity + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230701144244_EntryPermissionEntity.cs b/src/Infrastructure/Persistence/Migrations/20230701144244_EntryPermissionEntity.cs new file mode 100644 index 00000000..71916564 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701144244_EntryPermissionEntity.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class EntryPermissionEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "EntryPermissions", + columns: table => new + { + EmployeeId = table.Column(type: "uuid", nullable: false), + EntryId = table.Column(type: "uuid", nullable: false), + AllowedOperations = table.Column(type: "text", nullable: false), + ExpiryDateTime = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EntryPermissions", x => new { x.EntryId, x.EmployeeId }); + table.ForeignKey( + name: "FK_EntryPermissions_Entries_EntryId", + column: x => x.EntryId, + principalTable: "Entries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_EntryPermissions_Users_EmployeeId", + column: x => x.EmployeeId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_EntryPermissions_EmployeeId", + table: "EntryPermissions", + column: "EmployeeId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EntryPermissions"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230701174913_AddSizeEntry.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230701174913_AddSizeEntry.Designer.cs new file mode 100644 index 00000000..0433043c --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701174913_AddSizeEntry.Designer.cs @@ -0,0 +1,1100 @@ +// +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("20230701174913_AddSizeEntry")] + partial class AddSizeEntry + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230701174913_AddSizeEntry.cs b/src/Infrastructure/Persistence/Migrations/20230701174913_AddSizeEntry.cs new file mode 100644 index 00000000..459b3a39 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230701174913_AddSizeEntry.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddSizeEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Size", + table: "Entries", + type: "bigint", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Size", + table: "Entries"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230704141609_AddIsSharedRoot.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230704141609_AddIsSharedRoot.Designer.cs new file mode 100644 index 00000000..25988b79 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230704141609_AddIsSharedRoot.Designer.cs @@ -0,0 +1,1141 @@ +// +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("20230704141609_AddIsSharedRoot")] + partial class AddIsSharedRoot + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsSharedRoot") + .HasColumnType("boolean"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230704141609_AddIsSharedRoot.cs b/src/Infrastructure/Persistence/Migrations/20230704141609_AddIsSharedRoot.cs new file mode 100644 index 00000000..7d3ff918 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230704141609_AddIsSharedRoot.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class AddIsSharedRoot : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsSharedRoot", + table: "EntryPermissions", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsSharedRoot", + table: "EntryPermissions"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230708045158_renameSizeEntry.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230708045158_renameSizeEntry.Designer.cs new file mode 100644 index 00000000..df612dbb --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230708045158_renameSizeEntry.Designer.cs @@ -0,0 +1,1100 @@ +// +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("20230708045158_renameSizeEntry")] + partial class renameSizeEntry + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeInBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230708045158_renameSizeEntry.cs b/src/Infrastructure/Persistence/Migrations/20230708045158_renameSizeEntry.cs new file mode 100644 index 00000000..9d233696 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230708045158_renameSizeEntry.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class renameSizeEntry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Size", + table: "Entries", + newName: "SizeInBytes"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "SizeInBytes", + table: "Entries", + newName: "Size"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230711074257_DeleteLogEntities.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230711074257_DeleteLogEntities.Designer.cs new file mode 100644 index 00000000..680ed43d --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230711074257_DeleteLogEntities.Designer.cs @@ -0,0 +1,886 @@ +// +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("20230711074257_DeleteLogEntities")] + partial class DeleteLogEntities + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeInBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsSharedRoot") + .HasColumnType("boolean"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230711074257_DeleteLogEntities.cs b/src/Infrastructure/Persistence/Migrations/20230711074257_DeleteLogEntities.cs new file mode 100644 index 00000000..900c2b64 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230711074257_DeleteLogEntities.cs @@ -0,0 +1,228 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class DeleteLogEntities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DocumentLogs"); + + migrationBuilder.DropTable( + name: "FolderLogs"); + + migrationBuilder.DropTable( + name: "LockerLogs"); + + migrationBuilder.DropTable( + name: "RequestLogs"); + + migrationBuilder.DropTable( + name: "RoomLogs"); + + migrationBuilder.DropTable( + name: "UserLogs"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DocumentLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + BaseFolderId = table.Column(type: "uuid", nullable: true), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", 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_Folders_BaseFolderId", + column: x => x.BaseFolderId, + principalTable: "Folders", + 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), + BaseLockerId = table.Column(type: "uuid", nullable: true), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", 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_Lockers_BaseLockerId", + column: x => x.BaseLockerId, + principalTable: "Lockers", + 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), + BaseRoomId = table.Column(type: "uuid", nullable: true), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", 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_Rooms_BaseRoomId", + column: x => x.BaseRoomId, + principalTable: "Rooms", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_LockerLogs_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RequestLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + Time = table.Column(type: "timestamp without time zone", nullable: false), + Type = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestLogs", x => x.Id); + table.ForeignKey( + name: "FK_RequestLogs_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), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", 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_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserLogs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + Action = table.Column(type: "text", nullable: false), + ObjectId = table.Column(type: "uuid", nullable: true), + 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_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_BaseFolderId", + table: "DocumentLogs", + column: "BaseFolderId"); + + migrationBuilder.CreateIndex( + name: "IX_DocumentLogs_UserId", + table: "DocumentLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_BaseLockerId", + table: "FolderLogs", + column: "BaseLockerId"); + + migrationBuilder.CreateIndex( + name: "IX_FolderLogs_UserId", + table: "FolderLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_BaseRoomId", + table: "LockerLogs", + column: "BaseRoomId"); + + migrationBuilder.CreateIndex( + name: "IX_LockerLogs_UserId", + table: "LockerLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RequestLogs_UserId", + table: "RequestLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RoomLogs_UserId", + table: "RoomLogs", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserLogs_UserId", + table: "UserLogs", + column: "UserId"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230711090539_EntryPermissionExpiryDateNowIsNullable.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230711090539_EntryPermissionExpiryDateNowIsNullable.Designer.cs new file mode 100644 index 00000000..342f941d --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230711090539_EntryPermissionExpiryDateNowIsNullable.Designer.cs @@ -0,0 +1,1144 @@ +// +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("20230711090539_EntryPermissionExpiryDateNowIsNullable")] + partial class EntryPermissionExpiryDateNowIsNullable + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeInBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsSharedRoot") + .HasColumnType("boolean"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230711090539_EntryPermissionExpiryDateNowIsNullable.cs b/src/Infrastructure/Persistence/Migrations/20230711090539_EntryPermissionExpiryDateNowIsNullable.cs new file mode 100644 index 00000000..c9133eb5 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230711090539_EntryPermissionExpiryDateNowIsNullable.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class EntryPermissionExpiryDateNowIsNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ExpiryDateTime", + table: "EntryPermissions", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(LocalDateTime), + oldType: "timestamp without time zone"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ExpiryDateTime", + table: "EntryPermissions", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0), + oldClrType: typeof(LocalDateTime), + oldType: "timestamp without time zone", + oldNullable: true); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230712085900_EntryOldPath.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230712085900_EntryOldPath.Designer.cs new file mode 100644 index 00000000..7896fe38 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230712085900_EntryOldPath.Designer.cs @@ -0,0 +1,889 @@ +// +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("20230712085900_EntryOldPath")] + partial class EntryOldPath + { + /// + 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("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + 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("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OldPath") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeInBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsSharedRoot") + .HasColumnType("boolean"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + 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("FileId") + .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("FileId") + .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("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + + 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.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + + 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("File"); + + 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("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/20230712085900_EntryOldPath.cs b/src/Infrastructure/Persistence/Migrations/20230712085900_EntryOldPath.cs new file mode 100644 index 00000000..216592b4 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230712085900_EntryOldPath.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class EntryOldPath : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OldPath", + table: "Entries", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "OldPath", + table: "Entries"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index a69d09d2..68850ac9 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -23,6 +23,43 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Application.Common.Models.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Event") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("ObjectType") + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + modelBuilder.Entity("Domain.Entities.Department", b => { b.Property("Id") @@ -41,28 +78,150 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Departments"); }); + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OldPath") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeInBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("FileId") + .IsUnique(); + + b.HasIndex("OwnerId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsSharedRoot") + .HasColumnType("boolean"); + + b.HasKey("EntryId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("EntryPermissions"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileExtension") + .HasColumnType("text"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + 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("Reason") + 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"); @@ -78,6 +237,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"); @@ -90,12 +255,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(64) .HasColumnType("character varying(64)"); + b.Property("FileId") + .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) @@ -105,6 +285,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DepartmentId"); + b.HasIndex("FileId") + .IsUnique(); + b.HasIndex("FolderId"); b.HasIndex("ImporterId"); @@ -121,6 +304,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)"); @@ -128,6 +317,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"); @@ -146,6 +341,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") @@ -155,6 +395,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)"); @@ -162,6 +408,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) @@ -180,6 +432,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") @@ -189,6 +463,15 @@ 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"); + b.Property("Description") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -196,6 +479,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) @@ -208,6 +497,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasAlternateKey("Name"); + b.HasIndex("DepartmentId"); + b.ToTable("Rooms"); }); @@ -218,7 +509,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("UserId"); - b.Property("RoomId") + b.Property("RoomId") .HasColumnType("uuid"); b.HasKey("Id"); @@ -265,6 +556,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -281,6 +595,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("Email") + .IsRequired() .HasMaxLength(320) .HasColumnType("character varying(320)"); @@ -309,6 +624,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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)"); @@ -330,6 +650,50 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Users"); }); + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.User", "Uploader") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.HasOne("Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("Owner"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.EntryPermission", b => + { + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithMany() + .HasForeignKey("EntryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Entry"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => { b.HasOne("Domain.Entities.User", "Borrower") @@ -355,6 +719,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .WithMany() .HasForeignKey("DepartmentId"); + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "FileId"); + b.HasOne("Domain.Entities.Physical.Folder", "Folder") .WithMany("Documents") .HasForeignKey("FolderId"); @@ -365,6 +733,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Department"); + b.Navigation("File"); + b.Navigation("Folder"); b.Navigation("Importer"); @@ -381,6 +751,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") @@ -392,6 +781,36 @@ 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") + .WithMany("Rooms") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => { b.HasOne("Domain.Entities.User", "User") @@ -402,9 +821,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Domain.Entities.Physical.Room", "Room") .WithOne("Staff") - .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); b.Navigation("Room"); @@ -422,6 +839,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -431,6 +859,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Department"); }); + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Rooms"); + }); + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => { b.Navigation("Documents"); 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/MailService.cs b/src/Infrastructure/Services/MailService.cs new file mode 100644 index 00000000..ca280627 --- /dev/null +++ b/src/Infrastructure/Services/MailService.cs @@ -0,0 +1,139 @@ +using System.Text.Json; +using Application.Common.Interfaces; +using Application.Common.Models; +using Infrastructure.Shared; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using RestSharp; + +namespace Infrastructure.Services; + +public class MailService : IMailService +{ + private readonly MailSettings _mailSettings; + + public MailService(IOptions mailSettingsOptions) + { + _mailSettings = mailSettingsOptions.Value; + } + + public bool SendResetPasswordHtmlMail(string userEmail, string temporaryPassword, string token) + { + var data = new HtmlMailData() + { + From = new From() + { + Email = _mailSettings.SenderEmail, + Name = _mailSettings.SenderName, + }, + To = new To[] + { + new (){ Email = userEmail }, + }, + TemplateUuid = _mailSettings.TemplateUuids.ResetPassword, + TemplateVariables = new ResetPasswordTemplateVariables() + { + UserEmail = userEmail, + UserPassword = temporaryPassword, + Token = token + }, + }; + + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + + var client = new RestClient(_mailSettings.ClientUrl); + var request = new RestRequest + { + Method = Method.Post + }; + + request.AddHeader("Authorization", $"{JwtBearerDefaults.AuthenticationScheme} {_mailSettings.Token}"); + request.AddHeader("Content-Type", "application/json"); + request.AddParameter("application/json", json, ParameterType.RequestBody); + var response = client.Execute(request); + return response.IsSuccessStatusCode; + } + + public bool SendShareEntryHtmlMail(bool isDirectory, string name, string sharerName, string operation, string ownerName, + string email, string path) + { + var data = new HtmlMailData() + { + From = new From() + { + Email = _mailSettings.SenderEmail, + Name = _mailSettings.SenderName, + }, + To = new To[] + { + new (){ Email = email }, + }, + TemplateUuid = _mailSettings.TemplateUuids.ShareEntry, + TemplateVariables = new ShareEntryTemplateVariables() + { + EntryName = name, + Operation = operation, + EntryType = isDirectory ? "Folder" : "File", + OwnerName = ownerName, + SharerName = sharerName, + Path = path + }, + }; + + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + + var client = new RestClient(_mailSettings.ClientUrl); + var request = new RestRequest + { + Method = Method.Post + }; + + request.AddHeader("Authorization", $"{JwtBearerDefaults.AuthenticationScheme} {_mailSettings.Token}"); + request.AddHeader("Content-Type", "application/json"); + request.AddParameter("application/json", json, ParameterType.RequestBody); + var response = client.Execute(request); + return response.IsSuccessStatusCode; + } + + public bool SendCreateRequestHtmlMail(string userName, string requestType, string operation, string documentName, + string reason, Guid documentId, string email) + { + var data = new HtmlMailData() + { + From = new From() + { + Email = _mailSettings.SenderEmail, + Name = _mailSettings.SenderName, + }, + To = new To[] + { + new (){ Email = email }, + }, + TemplateUuid = _mailSettings.TemplateUuids.Request, + TemplateVariables = new CreateRequestTemplateVariables() + { + Operation = operation, + Reason = reason, + DocumentName = documentName, + UserName = userName, + Id = documentId.ToString(), + RequestType = requestType, + Path = !requestType.Equals("borrow request") ? "import/manage" : "requests" + }, + }; + + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions(JsonSerializerDefaults.Web)); + + var client = new RestClient(_mailSettings.ClientUrl); + var request = new RestRequest + { + Method = Method.Post + }; + + request.AddHeader("Authorization", $"{JwtBearerDefaults.AuthenticationScheme} {_mailSettings.Token}"); + request.AddHeader("Content-Type", "application/json"); + request.AddParameter("application/json", json, ParameterType.RequestBody); + var response = client.Execute(request); + return response.IsSuccessStatusCode; + } +} \ 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/src/Infrastructure/Services/SecurityService.cs b/src/Infrastructure/Services/SecurityService.cs new file mode 100644 index 00000000..9e38f89e --- /dev/null +++ b/src/Infrastructure/Services/SecurityService.cs @@ -0,0 +1,21 @@ +using Application.Common.Interfaces; +using Application.Helpers; +using Infrastructure.Shared; +using Microsoft.Extensions.Options; + +namespace Infrastructure.Services; + +public class SecurityService : ISecurityService +{ + private readonly SecuritySettings _securitySettings; + + public SecurityService(IOptions securitySettingsOptions) + { + _securitySettings = securitySettingsOptions.Value; + } + + public string Hash(string input, string salt) + { + return input.HashPasswordWith(salt, _securitySettings.Pepper); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Shared/MailSettings.cs b/src/Infrastructure/Shared/MailSettings.cs new file mode 100644 index 00000000..da97ad6b --- /dev/null +++ b/src/Infrastructure/Shared/MailSettings.cs @@ -0,0 +1,18 @@ +namespace Infrastructure.Shared; + +public class MailSettings +{ + public string ClientUrl { get; set; } + public string Token { get; set; } + public string SenderName { get; set; } + public string SenderEmail { get; set; } + + public Template TemplateUuids { get; set; } +} + +public class Template +{ + public string ResetPassword { get; set; } + public string ShareEntry { get; set; } + public string Request { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/Shared/SecuritySettings.cs b/src/Infrastructure/Shared/SecuritySettings.cs new file mode 100644 index 00000000..beba3ec4 --- /dev/null +++ b/src/Infrastructure/Shared/SecuritySettings.cs @@ -0,0 +1,6 @@ +namespace Infrastructure.Shared; + +public class SecuritySettings +{ + public string Pepper { get; set; } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Application.Tests.Integration.csproj b/tests/Application.Tests.Integration/Application.Tests.Integration.csproj index 90eb4752..841efbdd 100644 --- a/tests/Application.Tests.Integration/Application.Tests.Integration.csproj +++ b/tests/Application.Tests.Integration/Application.Tests.Integration.csproj @@ -38,4 +38,8 @@ + + + + diff --git a/tests/Application.Tests.Integration/BaseClassFixture.cs b/tests/Application.Tests.Integration/BaseClassFixture.cs index 3411d01a..d724d1e5 100644 --- a/tests/Application.Tests.Integration/BaseClassFixture.cs +++ b/tests/Application.Tests.Integration/BaseClassFixture.cs @@ -1,13 +1,16 @@ -using Application.Departments.Commands.CreateDepartment; +using System.Text; +using Application.Helpers; using Bogus; using Domain.Common; using Domain.Entities; +using Domain.Entities.Digital; using Domain.Entities.Physical; -using FluentAssertions; +using Domain.Statuses; using Infrastructure.Persistence; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using NodaTime; using Xunit; namespace Application.Tests.Integration; @@ -15,19 +18,16 @@ namespace Application.Tests.Integration; [Collection(nameof(BaseCollectionFixture))] public class BaseClassFixture { - protected readonly Faker _departmentGenerator = new Faker() - .RuleFor(x => x.Name, faker => faker.Commerce.Department()); - - protected static IServiceScopeFactory _scopeFactory = null!; + protected static IServiceScopeFactory ScopeFactory = null!; protected BaseClassFixture(CustomApiFactory apiFactory) { - _scopeFactory = apiFactory.Services.GetRequiredService(); + ScopeFactory = apiFactory.Services.GetRequiredService(); } protected static async Task SendAsync(IRequest request) { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var mediator = scope.ServiceProvider.GetRequiredService(); @@ -36,7 +36,7 @@ protected static async Task SendAsync(IRequest protected void Remove(TEntity entity) where TEntity : BaseEntity? { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); @@ -47,7 +47,7 @@ protected void Remove(TEntity entity) where TEntity : BaseEntity? protected static async Task FindAsync(params object[] keyValues) where TEntity : class { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); @@ -57,7 +57,7 @@ protected void Remove(TEntity entity) where TEntity : BaseEntity? protected static async Task AddAsync(TEntity entity) where TEntity : class { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); @@ -69,7 +69,7 @@ protected static async Task AddAsync(TEntity entity) protected static async Task Add(TEntity entity) where TEntity : class { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); @@ -80,7 +80,7 @@ protected static async Task Add(TEntity entity) protected static async Task CountAsync() where TEntity : class { - using var scope = _scopeFactory.CreateScope(); + using var scope = ScopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); @@ -142,7 +142,7 @@ protected Locker CreateLocker(params Folder[] folders) return locker; } - protected Room CreateRoom(params Locker[] lockers) + protected Room CreateRoom(Department department, params Locker[] lockers) { var room = new Room() { @@ -150,7 +150,9 @@ protected Room CreateRoom(params Locker[] lockers) Name = new Faker().Commerce.ProductName(), Capacity = 3, NumberOfLockers = lockers.Length, - IsAvailable = true + IsAvailable = true, + Department = department, + DepartmentId = department.Id, }; foreach (var locker in lockers) @@ -160,4 +162,80 @@ protected Room CreateRoom(params Locker[] lockers) return room; } + + protected static Department CreateDepartment() + { + return new Department() + { + Id = Guid.NewGuid(), + Name = new Faker().Random.Word() + }; + } + + protected static User CreateUser(string role, string password) + { + var salt = StringUtil.RandomSalt(); + + return new User() + { + Id = Guid.NewGuid(), + Username = new Faker().Person.UserName, + Email = new Faker().Person.Email, + FirstName = new Faker().Person.FirstName, + LastName = new Faker().Person.LastName, + Role = role, + Position = new Faker().Random.Word(), + IsActivated = true, + IsActive = true, + Created = LocalDateTime.FromDateTime(DateTime.Now), + PasswordHash = password.HashPasswordWith(salt, "random pepper"), + PasswordSalt = salt + }; + } + + protected static Staff CreateStaff(User user, Room? room) + { + return new Staff() + { + Id = user.Id, + User = user, + Room = room, + }; + } + + protected static FileEntity CreateFile() + { + return new FileEntity() + { + Id = Guid.NewGuid(), + FileType = new Faker().Database.Type(), + FileData = Encoding.ASCII.GetBytes(new Faker().Lorem.Random.Words()) + }; + } + + protected static Entry CreateEntry(FileEntity file) + { + return new Entry() + { + Id = Guid.NewGuid(), + Name = new Faker().Commerce.ProductName(), + File = file, + Path = new Faker().Commerce.ProductDescription(), + FileId = file.Id, + }; + } + + protected static Borrow CreateBorrowRequest(User borrower, Document document, BorrowRequestStatus status) + { + return new Borrow() + { + Id = Guid.NewGuid(), + Borrower = borrower, + Document = document, + BorrowReason = "something something", + Status = status, + BorrowTime = LocalDateTime.FromDateTime(DateTime.Now), + DueTime = LocalDateTime.FromDateTime(DateTime.Now + TimeSpan.FromDays(1)) + }; + } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs new file mode 100644 index 00000000..c163f4f6 --- /dev/null +++ b/tests/Application.Tests.Integration/Borrows/Commands/ApproveBorrowRequestTests.cs @@ -0,0 +1,37 @@ +using Application.Borrows.Commands; +using Application.Common.Exceptions; +using Application.Identity; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentAssertions; +using Infrastructure.Persistence; +using Microsoft.Extensions.DependencyInjection; +using NodaTime; +using Xunit; + +namespace Application.Tests.Integration.Borrows.Commands; + +public class ApproveBorrowRequestTests : BaseClassFixture +{ + public ApproveBorrowRequestTests(CustomApiFactory apiFactory) : base(apiFactory) + { + + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenRequestDoesNotExist() + { + // Arrange + var command = new ApproveOrRejectBorrowRequest.Command() + { + BorrowId = Guid.NewGuid(), + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Borrow request does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs new file mode 100644 index 00000000..fdf816d0 --- /dev/null +++ b/tests/Application.Tests.Integration/Borrows/Commands/BorrowDocumentTests.cs @@ -0,0 +1,262 @@ +using Application.Borrows.Commands; +using Application.Common.Exceptions; +using Application.Identity; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentAssertions; +using Infrastructure.Persistence; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Application.Tests.Integration.Borrows.Commands; + +public class BorrowDocumentTests : BaseClassFixture +{ + public BorrowDocumentTests(CustomApiFactory apiFactory) : base(apiFactory) + { + + } + + [Fact] + public async Task ShouldCreateBorrowRequest_WhenDetailsAreValid() + { + // Arrange + using var scope = ScopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var department = CreateDepartment(); + await context.AddAsync(department); + + var user = CreateUser(IdentityData.Roles.Employee, "aaaaaa"); + user.Department = department; + await context.AddAsync(user); + + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Available; + document.Department = department; + await context.AddAsync(document); + await context.SaveChangesAsync(); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = document.Id, + BorrowReason = "Example", + BorrowFrom = DateTime.Now.Add(TimeSpan.FromHours(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(1)), + }; + + // Act + var result = await SendAsync(command); + + // Assert + result.DocumentId.Should().Be(command.DocumentId); + result.BorrowerId.Should().Be(command.BorrowerId); + 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()); + + // Cleanup + Remove(await FindAsync(result.Id)); + Remove(user); + Remove(document); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenUserDoesNotExist() + { + // Arrange + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Available; + await AddAsync(document); + + var command = new BorrowDocument.Command() + { + BorrowerId = Guid.NewGuid(), + DocumentId = document.Id, + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("User does not exist."); + + // Cleanup + Remove(document); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenUserIsNotActive() + { + // Arrange + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Available; + await AddAsync(document); + + var user = CreateUser(IdentityData.Roles.Employee, "aaaaaa"); + user.IsActive = false; + await AddAsync(user); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = document.Id, + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("User is not active."); + + // Cleanup + Remove(document); + Remove(user); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenUserIsNotActivated() + { + // Arrange + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Available; + await AddAsync(document); + + var user = CreateUser(IdentityData.Roles.Employee, "aaaaaa"); + user.IsActivated = false; + await AddAsync(user); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = document.Id, + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("User is not activated."); + + // Cleanup + Remove(document); + Remove(user); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenDocumentDoesNotExist() + { + // Arrange + var user = CreateUser(IdentityData.Roles.Employee, "bbbbbb"); + await AddAsync(user); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = Guid.NewGuid(), + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Document does not exist."); + + // Cleanup + Remove(user); + } + + [Fact] + public async Task ShouldConflictException_WhenDocumentIsNotAvailable() + { + // Arrange + var user = CreateUser(IdentityData.Roles.Employee, "bbbbbb"); + await AddAsync(user); + + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Borrowed; + await AddAsync(document); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = document.Id, + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Document is not available."); + + // Cleanup + Remove(user); + Remove(document); + } + + [Fact] + public async Task ShouldConflictException_WhenUserAndDocumentDoesNotBelongToTheSameDepartment() + { + // Arrange + using var scope = ScopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + + var user = CreateUser(IdentityData.Roles.Employee, "bbbbbb"); + user.Department = department1; + await context.AddAsync(user); + + var document = CreateNDocuments(1).First(); + document.Status = DocumentStatus.Available; + document.Department = department2; + await context.AddAsync(document); + + await context.SaveChangesAsync(); + + var command = new BorrowDocument.Command() + { + BorrowerId = user.Id, + DocumentId = document.Id, + BorrowFrom = DateTime.Now.Add(TimeSpan.FromDays(1)), + BorrowTo = DateTime.Now.Add(TimeSpan.FromDays(2)), + BorrowReason = "Example", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("User is not allowed to borrow this document."); + + // Cleanup + Remove(user); + Remove(document); + Remove(department1); + Remove(department2); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs b/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs new file mode 100644 index 00000000..247c4604 --- /dev/null +++ b/tests/Application.Tests.Integration/Borrows/Commands/UpdateBorrowTests.cs @@ -0,0 +1,40 @@ +using Application.Borrows.Commands; +using Application.Common.Exceptions; +using Application.Identity; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentAssertions; +using Infrastructure.Persistence; +using Microsoft.Extensions.DependencyInjection; +using NodaTime; +using Xunit; + +namespace Application.Tests.Integration.Borrows.Commands; + +public class UpdateBorrowTests : BaseClassFixture +{ + public UpdateBorrowTests(CustomApiFactory apiFactory) : base(apiFactory) + { + + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenRequestDoesNotExist() + { + // Arrange + var command = new UpdateBorrow.Command() + { + BorrowReason = "adsda", + BorrowFrom = DateTime.Now.AddHours(1), + BorrowTo = DateTime.Now.AddHours(2), + BorrowId = Guid.NewGuid(), + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Borrow request does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/CustomApiFactory.cs b/tests/Application.Tests.Integration/CustomApiFactory.cs index 04d44731..0ec3bd9f 100644 --- a/tests/Application.Tests.Integration/CustomApiFactory.cs +++ b/tests/Application.Tests.Integration/CustomApiFactory.cs @@ -1,4 +1,5 @@ using Api; +using Application.Common.Interfaces; using Infrastructure.Persistence; using Infrastructure.Shared; using Microsoft.AspNetCore.Hosting; @@ -16,23 +17,32 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices((builderContext, services) => { - var descriptor = services.SingleOrDefault( - d => d.ServiceType == - typeof(DbContextOptions)); - - if (descriptor != null) - { - services.Remove(descriptor); - } + Remove>(services); var databaseSettings = GetConfiguration().GetSection(nameof(DatabaseSettings)).Get(); services.AddDbContext(options => { - options.UseNpgsql(databaseSettings.ConnectionString, optionsBuilder => optionsBuilder.UseNodaTime()); + options.UseNpgsql(databaseSettings!.ConnectionString, optionsBuilder => optionsBuilder.UseNodaTime()); }); + + Remove(services); + services.AddTransient(); }); } + private static void Remove(IServiceCollection services) + where T : class + { + var descriptor = services.SingleOrDefault( + d => d.ServiceType == + typeof(T)); + + if (descriptor != null) + { + services.Remove(descriptor); + } + } + private IConfiguration GetConfiguration() { return new ConfigurationBuilder() diff --git a/tests/Application.Tests.Integration/CustomMailService.cs b/tests/Application.Tests.Integration/CustomMailService.cs new file mode 100644 index 00000000..587cf774 --- /dev/null +++ b/tests/Application.Tests.Integration/CustomMailService.cs @@ -0,0 +1,23 @@ +using Application.Common.Interfaces; + +namespace Application.Tests.Integration; + +public class CustomMailService : IMailService +{ + public bool SendResetPasswordHtmlMail(string userEmail, string temporaryPassword, string token) + { + return true; + } + + public bool SendShareEntryHtmlMail(bool isDirectory, string name, string sharerName, string operation, string ownerName, + string email, string path) + { + return true; + } + + public bool SendCreateRequestHtmlMail(string userName, string requestType, string operation, string documentName, + string reason, Guid documentId, string email) + { + return true; + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Departments/Commands/CreateDepartmentTests.cs b/tests/Application.Tests.Integration/Departments/Commands/AddDepartmentTests.cs similarity index 61% rename from tests/Application.Tests.Integration/Departments/Commands/CreateDepartmentTests.cs rename to tests/Application.Tests.Integration/Departments/Commands/AddDepartmentTests.cs index 0e41af6c..dc3d31e1 100644 --- a/tests/Application.Tests.Integration/Departments/Commands/CreateDepartmentTests.cs +++ b/tests/Application.Tests.Integration/Departments/Commands/AddDepartmentTests.cs @@ -1,22 +1,25 @@ using Application.Common.Exceptions; -using Application.Departments.Commands.DeleteDepartment; +using Application.Departments.Commands; using Domain.Entities; using FluentAssertions; using Xunit; namespace Application.Tests.Integration.Departments.Commands; -public class CreateDepartmentTests : BaseClassFixture +public class AddDepartmentTests : BaseClassFixture { - public CreateDepartmentTests(CustomApiFactory apiFactory) : base(apiFactory) + public AddDepartmentTests(CustomApiFactory apiFactory) : base(apiFactory) { } - [Fact(Timeout = 200)] + [Fact] public async Task ShouldCreateDepartment_WhenDepartmentNameIsValid() { // Arrange - var createDepartmentCommand = _departmentGenerator.Generate(); + var createDepartmentCommand = new AddDepartment.Command() + { + Name = "something", + }; // Act var department = await SendAsync(createDepartmentCommand); @@ -33,14 +36,19 @@ public async Task ShouldCreateDepartment_WhenDepartmentNameIsValid() public async Task ShouldReturnConflict_WhenDepartmentNameHasExisted() { // Arrange - var createDepartmentCommand = _departmentGenerator.Generate(); - var department = await SendAsync(createDepartmentCommand); + var department = CreateDepartment(); + await AddAsync(department); + + var command = new AddDepartment.Command() + { + Name = department.Name, + }; // Act - var action = async () => await SendAsync(createDepartmentCommand); + var action = async () => await SendAsync(command); // Assert - await action.Should().ThrowAsync().WithMessage("Department name already exists"); + await action.Should().ThrowAsync().WithMessage("Department name already exists."); // Cleanup var departmentEntity = await FindAsync(department.Id); diff --git a/tests/Application.Tests.Integration/Departments/Queries/GetAllDepartmentsTests.cs b/tests/Application.Tests.Integration/Departments/Queries/GetAllDepartmentsTests.cs index 6bd82481..8f5ba752 100644 --- a/tests/Application.Tests.Integration/Departments/Queries/GetAllDepartmentsTests.cs +++ b/tests/Application.Tests.Integration/Departments/Queries/GetAllDepartmentsTests.cs @@ -1,6 +1,6 @@ using Application.Common.Mappings; -using Application.Departments.Queries.GetAllDepartments; -using Application.Identity; +using Application.Common.Models.Dtos; +using Application.Departments.Queries; using Application.Users.Queries; using AutoMapper; using Bogus; @@ -23,20 +23,19 @@ public GetAllDepartmentsTests(CustomApiFactory apiFactory) : base(apiFactory) public async Task ShouldReturnDepartments_WhenDepartmentsExist() { // Arrange - var department = new Department() { Id = Guid.NewGuid(), Name = new Faker().Commerce.Department() }; await AddAsync(department); - var query = new GetAllDepartmentsQuery(); + var query = new GetAllDepartments.Query(); // Act var result = await SendAsync(query); // Assert - result.Should().ContainEquivalentOf(_mapper.Map(department)); + result.Items.Should().ContainEquivalentOf(_mapper.Map(department)); // Cleanup Remove(department); @@ -46,13 +45,12 @@ public async Task ShouldReturnDepartments_WhenDepartmentsExist() public async Task ShouldReturnEmptyList_WhenNoDepartmentsExist() { // Arrange - var query = new GetAllDepartmentsQuery(); + var query = new GetAllDepartments.Query(); // Act var result = await SendAsync(query); // Assert - result.Count().Should().Be(1); - result.First().Name.Should().Be(IdentityData.Roles.Admin); + result.Items.Count().Should().Be(0); } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Documents/Commands/DeleteDocumentTests.cs b/tests/Application.Tests.Integration/Documents/Commands/DeleteDocumentTests.cs new file mode 100644 index 00000000..da182337 --- /dev/null +++ b/tests/Application.Tests.Integration/Documents/Commands/DeleteDocumentTests.cs @@ -0,0 +1,31 @@ +using Application.Documents.Commands; +using Domain.Entities.Physical; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Documents.Commands; + +public class DeleteDocumentTests : BaseClassFixture +{ + public DeleteDocumentTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatDocumentDoesNotExist() + { + // Arrange + var command = new DeleteDocument.Command() + { + DocumentId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Document does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Documents/Commands/UpdateDocumentTests.cs b/tests/Application.Tests.Integration/Documents/Commands/UpdateDocumentTests.cs new file mode 100644 index 00000000..67050414 --- /dev/null +++ b/tests/Application.Tests.Integration/Documents/Commands/UpdateDocumentTests.cs @@ -0,0 +1,36 @@ +using Application.Common.Exceptions; +using Application.Documents.Commands; +using Application.Identity; +using FluentAssertions; +using Infrastructure.Persistence; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Application.Tests.Integration.Documents.Commands; + +public class UpdateDocumentTests : BaseClassFixture +{ + public UpdateDocumentTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatDocumentDoesNotExist() + { + // Arrange + var command = new UpdateDocument.Command() + { + DocumentId = Guid.NewGuid(), + Title = "Khoa is ngu", + Description = "This would probably not be duplicated", + DocumentType = "Hehehe", + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Document does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentTypesTests.cs b/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentTypesTests.cs index bf83e5f5..672aec9a 100644 --- a/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentTypesTests.cs +++ b/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentTypesTests.cs @@ -1,6 +1,4 @@ -using Application.Documents.Queries.GetDocumentTypes; -using Bogus; -using Domain.Entities.Physical; +using Application.Documents.Queries; using FluentAssertions; using Xunit; @@ -16,14 +14,9 @@ public GetAllDocumentTypesTests(CustomApiFactory apiFactory) : base(apiFactory) public async Task ShouldReturnDocumentTypes_WhenDocumentTypesExist() { // Arrange - var document = new Document() - { - Id = Guid.NewGuid(), - Title = new Faker().Name.JobTitle(), - DocumentType = new Faker().Commerce.ProductName(), - }; + var document = CreateNDocuments(1).First(); await AddAsync(document); - var query = new GetAllDocumentTypesQuery(); + var query = new GetAllDocumentTypes.Query(); // Act var result = await SendAsync(query); @@ -39,7 +32,7 @@ public async Task ShouldReturnDocumentTypes_WhenDocumentTypesExist() public async Task ShouldReturnEmptyList_WhenNoDocumentTypesExist() { // Arrange - var query = new GetAllDocumentTypesQuery(); + var query = new GetAllDocumentTypes.Query(); // Act var result = await SendAsync(query); diff --git a/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentsPaginatedTests.cs b/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentsPaginatedTests.cs deleted file mode 100644 index b051e093..00000000 --- a/tests/Application.Tests.Integration/Documents/Queries/GetAllDocumentsPaginatedTests.cs +++ /dev/null @@ -1,422 +0,0 @@ - using Application.Common.Exceptions; - using Application.Common.Extensions; - using Application.Common.Mappings; - using Application.Common.Models.Dtos.Physical; - using Application.Documents.Queries.GetAllDocumentsPaginated; - using AutoMapper; - using Bogus; - using Domain.Entities.Physical; - using FluentAssertions; - using Xunit; - - namespace Application.Tests.Integration.Documents.Queries; - - public class GetAllDocumentsPaginatedTests : BaseClassFixture - { - private readonly IMapper _mapper; - public GetAllDocumentsPaginatedTests(CustomApiFactory apiFactory) : base(apiFactory) - { - var configuration = new MapperConfiguration(config => config.AddProfile()); - - _mapper = configuration.CreateMapper(); - } - - [Fact] - public async Task ShouldReturnAllDocuments_WhenNoContainersAreDefined() - { - // Arrange - var documents = CreateNDocuments(1); - var folder = CreateFolder(documents); - var locker = CreateLocker(folder); - var room = CreateRoom(locker); - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery(); - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().ContainEquivalentOf(_mapper.Map(documents.First())); - - // Cleanup - Remove(documents.First()); - Remove(folder); - Remove(locker); - Remove(room); - } - - [Fact] - public async Task ShouldReturnEmptyPaginatedList_WhenNoDocumentsExist() - { - // Arrange - var room = CreateRoom(); - - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id - }; - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().BeEmpty(); - - // Cleanup - Remove(room); - } - - [Fact] - public async Task ShouldReturnDocumentsOfRoom_WhenOnlyRoomIdIsPresent() - { - // Arrange - var documents1 = CreateNDocuments(2); - - var folder1 = CreateFolder(documents1); - - var locker1 = CreateLocker(folder1); - - var room1 = CreateRoom(locker1); - - var documents2 = CreateNDocuments(2); - - var folder2 = CreateFolder(documents2); - - var locker2 = CreateLocker(folder2); - - var room2 = CreateRoom(locker2); - - await AddAsync(room1); - await AddAsync(room2); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room1.Id - }; - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().ContainEquivalentOf(_mapper.Map(documents1[0])); - result.Items.Should().ContainEquivalentOf(_mapper.Map(documents1[1])); - result.Items.Should().NotContainEquivalentOf(_mapper.Map(documents2[0])); - result.Items.Should().NotContainEquivalentOf(_mapper.Map(documents2[1])); - - // Cleanup - Remove(documents1[0]); - Remove(documents1[1]); - Remove(documents2[0]); - Remove(documents2[1]); - Remove(folder1); - Remove(folder2); - Remove(locker1); - Remove(locker2); - Remove(room1); - Remove(room2); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenOnlyRoomIdIsPresentButDoesNotExist() - { - // Arrange - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(query); - - // Assert - await action.Should().ThrowAsync("Room does not exist."); - } - - [Fact] - public async Task ShouldReturnDocumentsOfLocker_WhenOnlyRoomIdAndLockerIdArePresentAndLockerIsInRoom() - { - // Arrange - var documents1 = CreateNDocuments(2); - - var folder1 = CreateFolder(documents1); - - var locker1 = CreateLocker(folder1); - - var documents2 = CreateNDocuments(2); - - var folder2 = CreateFolder(documents2); - - var locker2 = CreateLocker(folder2); - - var room = CreateRoom(locker1, locker2); - - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id, - LockerId = locker1.Id - }; - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().BeEquivalentTo(_mapper.Map>(documents1)); - result.Items.Should().NotContainEquivalentOf(_mapper.Map>(documents2)); - - // Cleanup - Remove(documents1[0]); - Remove(documents1[1]); - Remove(documents2[0]); - Remove(documents2[1]); - Remove(folder1); - Remove(folder2); - Remove(locker1); - Remove(locker2); - Remove(room); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenOnlyRoomIdAndLockerIdArePresentAndLockerDoesNotExist() - { - // Arrange - var room = CreateRoom(); - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id, - LockerId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(query); - - // Assert - await action.Should().ThrowAsync("Locker does not exist."); - - // Cleanup - Remove(room); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenOnlyRoomIdAndLockerIdArePresentAndLockerIsNotInRoom() - { - // Arrange - var locker = CreateLocker(); - - var room1 = CreateRoom(); - - var room2 = CreateRoom(locker); - - await AddAsync(room1); - await AddAsync(room2); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room1.Id, - LockerId = locker.Id - }; - - // Act - var action = async () => await SendAsync(query); - - // Assert - await action.Should().ThrowAsync("Room does not match locker."); - - // Cleanup - Remove(locker); - Remove(room1); - Remove(room2); - } - - [Fact] - public async Task ShouldReturnDocumentsOfFolder_WhenAllIdsArePresentAndFolderIsInBothLockerAndRoom() - { - // Arrange - var documents1 = CreateNDocuments(2); - var folder1 = CreateFolder(documents1); - - var documents2 = CreateNDocuments(2); - var folder2 = CreateFolder(documents2); - - var locker = CreateLocker(folder1, folder2); - var room = CreateRoom(locker); - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id, - LockerId = locker.Id, - FolderId = folder1.Id - }; - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().BeEquivalentTo(_mapper.Map(documents1)); - result.Items.Should().NotBeEquivalentTo(_mapper.Map(documents2)); - - // Cleanup - Remove(documents1[0]); - Remove(documents1[1]); - Remove(documents2[0]); - Remove(documents2[1]); - Remove(folder1); - Remove(folder2); - Remove(locker); - Remove(room); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenAllIdsArePresentAndValidAndFolderDoesNotExist() - { - // Arrange - var locker = CreateLocker(); - var room = CreateRoom(locker); - - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id, - LockerId = locker.Id, - FolderId = Guid.NewGuid() - }; - - // Act - var action = async () => await SendAsync(query); - - // Assert - await action.Should().ThrowAsync("Folder does not exist."); - - // Cleanup - Remove(locker); - Remove(room); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenAllIdsArePresentAndFolderIsNotInLockerOrInRoom() - { - // Arrange - var folder1 = CreateFolder(); - var locker1 = CreateLocker(folder1); - var room1 = CreateRoom(locker1); - - var folder2 = CreateFolder(); - var locker2 = CreateLocker(folder2); - var room2 = CreateRoom(locker2); - - await AddAsync(room1); - await AddAsync(room2); - - var query1 = new GetAllDocumentsPaginatedQuery() - { - RoomId = room1.Id, - LockerId = locker2.Id, - FolderId = folder1.Id - }; - - var query2 = new GetAllDocumentsPaginatedQuery() - { - RoomId = room1.Id, - LockerId = locker2.Id, - FolderId = folder2.Id - }; - - // Act - var action1 = async () => await SendAsync(query1); - var action2 = async () => await SendAsync(query2); - - // Assert - await action1.Should().ThrowAsync("Either locker or room does not match folder."); - await action1.Should().ThrowAsync("Either locker or room does not match folder."); - - // Cleanup - Remove(folder1); - Remove(folder2); - Remove(locker1); - Remove(locker2); - Remove(room1); - Remove(room2); - } - - [Fact] - public async Task ShouldReturnSortedByIdPaginatedList_WhenSortByIsNotPresent() - { - // Arrange - var documents = CreateNDocuments(2); - var folder = CreateFolder(documents); - var locker = CreateLocker(folder); - var room = CreateRoom(locker); - - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id - }; - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.First().Should().BeEquivalentTo(_mapper.Map(documents[0].Id.CompareTo(documents[1].Id) <= 0 ? documents[0] : documents[1])); - - // Cleanup - Remove(documents[0]); - Remove(documents[1]); - Remove(folder); - Remove(locker); - Remove(room); - } - - [Theory] - [InlineData(nameof(DocumentDto.Id), "asc")] - [InlineData(nameof(DocumentDto.Title), "asc")] - [InlineData(nameof(DocumentDto.DocumentType), "asc")] - [InlineData(nameof(DocumentDto.Description), "asc")] - [InlineData(nameof(DocumentDto.Id), "desc")] - [InlineData(nameof(DocumentDto.Title), "desc")] - [InlineData(nameof(DocumentDto.DocumentType), "desc")] - [InlineData(nameof(DocumentDto.Description), "desc")] - public async Task ShouldReturnSortedByPropertyPaginatedList_WhenSortByIsPresent(string sortBy, string sortOrder) - { - // Arrange - var documents = CreateNDocuments(2); - var folder = CreateFolder(documents); - var locker = CreateLocker(folder); - var room = CreateRoom(locker); - - await AddAsync(room); - - var query = new GetAllDocumentsPaginatedQuery() - { - RoomId = room.Id, - SortBy = sortBy, - SortOrder = sortOrder - }; - - var list = new EnumerableQuery(_mapper.Map>(documents)); - var list2 = list.OrderByCustom(sortBy, sortOrder); - var expected = list2.ToList(); - - // Act - var result = await SendAsync(query); - - // Assert - result.Items.Should().BeEquivalentTo(expected); - - // Cleanup - Remove(documents[0]); - Remove(documents[1]); - Remove(folder); - Remove(locker); - Remove(room); - } - } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Commands/AddFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/AddFolderTests.cs index db3c863c..34ce5db1 100644 --- a/tests/Application.Tests.Integration/Folders/Commands/AddFolderTests.cs +++ b/tests/Application.Tests.Integration/Folders/Commands/AddFolderTests.cs @@ -1,9 +1,6 @@ using Application.Common.Exceptions; -using Application.Common.Models.Dtos.Physical; -using Application.Folders.Commands.AddFolder; -using Application.Lockers.Commands.AddLocker; -using Application.Rooms.Commands.CreateRoom; -using Bogus; +using Application.Folders.Commands; +using Domain.Entities; using Domain.Entities.Physical; using Domain.Exceptions; using FluentAssertions; @@ -13,235 +10,62 @@ namespace Application.Tests.Integration.Folders.Commands; public class AddFolderTests : BaseClassFixture { - private readonly Faker _folderGenerator = new Faker() - .RuleFor(f => f.Name, faker => faker.Commerce.ProductName()) - .RuleFor(f => f.Description, faker => faker.Commerce.ProductDescription()) - .RuleFor(f => f.Capacity, faker => faker.Random.Int(1,9999)); - - private readonly Faker _roomGenerator = new Faker() - .RuleFor(r => r.Name, faker => faker.Commerce.ProductName()) - .RuleFor(r => r.Description, faker => faker.Commerce.ProductDescription()) - .RuleFor(r => r.Capacity, faker => faker.Random.Int(1,9999)); - - private readonly Faker _lockerGenerator = new Faker() - .RuleFor(l => l.Name, faker => faker.Commerce.ProductName()) - .RuleFor(l => l.Description, faker => faker.Commerce.ProductDescription()) - .RuleFor(l => l.Capacity, faker => faker.Random.Int(1,9999)); public AddFolderTests(CustomApiFactory apiFactory) : base(apiFactory) { } [Fact] - public async Task ShouldAddFolder_WhenAddDetailsAreValid() + public async Task ShouldThrowLimitExceededException_WhenGoingOverCapacity() { // Arrange - var addRoomCommand = _roomGenerator.Generate(); - var room = await SendAsync(addRoomCommand); + var department = CreateDepartment(); + var folder1 = CreateFolder(); + var folder2 = CreateFolder(); + var folder3 = CreateFolder(); + var locker = CreateLocker(folder1, folder2, folder3); + var room = CreateRoom(department, locker); + await AddAsync(room); - var addLockerCommand = _lockerGenerator.Generate(); - addLockerCommand = addLockerCommand with - { - RoomId = room.Id, - Capacity = 1 - }; - var locker = await SendAsync(addLockerCommand); - - var addFolderCommand = _folderGenerator.Generate(); - addFolderCommand = addFolderCommand with + var command = new AddFolder.Command() { + Name = "something", + Capacity = 3, LockerId = locker.Id, - Capacity = 1 - }; - - // Act - var folder = await SendAsync(addFolderCommand); - // Assert - folder.Name.Should().Be(addFolderCommand.Name); - folder.Description.Should().Be(addFolderCommand.Description); - folder.Capacity.Should().Be(addFolderCommand.Capacity); - folder.Locker.Id.Should().Be(locker.Id); - folder.Locker.NumberOfFolders.Should().Be(locker.NumberOfFolders + 1); - folder.NumberOfDocuments.Should().Be(0); - folder.IsAvailable.Should().BeTrue(); - - // Clean up - var folderEntity = await FindAsync(folder.Id); - var lockerEntity = await FindAsync(locker.Id); - var roomEntity = await FindAsync(room.Id); - Remove(folderEntity); - Remove(lockerEntity); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldAddFolder_WhenFoldersHasSameNameButInDifferentLockers() - { - // Arrange - var sameFolderName = new Faker().Commerce.ProductName(); - var addRoomCommand = _roomGenerator.Generate(); - addRoomCommand = addRoomCommand with - { - Capacity = 2 - }; - - var room = await SendAsync(addRoomCommand); - - var addLockerCommand = _lockerGenerator.Generate(); - - var addLockerACommand = addLockerCommand with - { - RoomId = room.Id, - Capacity = 1 - }; - var addLockerBCommand = addLockerCommand with - { - RoomId = room.Id, - Name = new Faker().Commerce.ProductName(), - Capacity = 1 - }; - - var lockerA = await SendAsync(addLockerACommand); - var lockerB = await SendAsync(addLockerBCommand); - - var addFolderCommand = _folderGenerator.Generate(); - var addFolderCommandForLockerA = addFolderCommand with - { - Name = sameFolderName, - LockerId = lockerA.Id - }; - var addFolderCommandForLockerB = addFolderCommand with - { - Name = sameFolderName, - LockerId = lockerB.Id - }; - var folderA = await SendAsync(addFolderCommandForLockerA); - - // Act - var folderB = await SendAsync(addFolderCommandForLockerB); - - // Assert - folderA.Locker.Id.Should().NotBe(folderB.Locker.Id); - folderA.Name.Should().Be(folderB.Name); - - // Cleanup - var folderAEntity = await FindAsync(folderA.Id); - var folderBEntity = await FindAsync(folderB.Id); - var lockerAEntity = await FindAsync(lockerA.Id); - var lockerBEntity = await FindAsync(lockerB.Id); - var roomEntity = await FindAsync(room.Id); - Remove(folderAEntity); - Remove(folderBEntity); - Remove(lockerAEntity); - Remove(lockerBEntity); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenFolderAlreadyExistsInTheSameLocker() - { - // Arrange - var sameFolderName = new Faker().Commerce.ProductName(); - var addRoomCommand = _roomGenerator.Generate(); - var room = await SendAsync(addRoomCommand); - - var addLockerCommand = _lockerGenerator.Generate(); - addLockerCommand = addLockerCommand with - { - RoomId = room.Id, - Capacity = 2 - }; - var locker = await SendAsync(addLockerCommand); - - var addFolderCommand = _folderGenerator.Generate(); - var addFolderCommandForLockerA = addFolderCommand with - { - Name = sameFolderName, - LockerId = locker.Id + Description = "something else", }; - var addFolderCommandForLockerB = addFolderCommand with - { - Name = sameFolderName, - LockerId = locker.Id - }; - var folder = await SendAsync(addFolderCommandForLockerA); // Act - var action = async () => await SendAsync(addFolderCommandForLockerB); - - // Assert - await action.Should().ThrowAsync().WithMessage("Folder's name already exists."); - - // Cleanup - var folderEntity = await FindAsync(folder.Id); - var lockerEntity = await FindAsync(locker.Id); - var roomEntity = await FindAsync(room.Id); - Remove(folderEntity); - Remove(lockerEntity); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldThrowLimitExceededException_WhenGoingOverCapacity() - { - // Arrange - var addRoomCommand = _roomGenerator.Generate(); - var room = await SendAsync(addRoomCommand); - - var addLockerCommand = _lockerGenerator.Generate(); - addLockerCommand = addLockerCommand with - { - RoomId = room.Id, - Capacity = new Faker().Random.Int(1,10) - }; - var locker = await SendAsync(addLockerCommand); - var list = new List(); - - // Act - var action = async () => - { - - for (var i = 0; i <= locker.Capacity; i++) - { - var addFolderCommand = _folderGenerator.Generate(); - addFolderCommand = addFolderCommand with - { - LockerId = locker.Id - }; - list.Add(await SendAsync(addFolderCommand)); - } - }; + var action = async () => await SendAsync(command); // Assert await action.Should().ThrowAsync() .WithMessage("This locker cannot accept more folders."); // Cleanup - foreach (var f in list) - { - var folderEntity = await FindAsync(f.Id); - Remove(folderEntity); - } - var lockerEntity = await FindAsync(locker.Id); - var roomEntity = await FindAsync(room.Id); - Remove(lockerEntity); - Remove(roomEntity); + Remove(folder1); + Remove(folder2); + Remove(folder3); + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); } [Fact] public async Task ShouldThrowKeyNotFoundException_WhenLockerIdNotExists() { // Arrange - var addFolderCommand = _folderGenerator.Generate(); - addFolderCommand = addFolderCommand with + var command = new AddFolder.Command() { - LockerId = Guid.NewGuid() + LockerId = Guid.NewGuid(), + Name = "something", + Capacity = 3 }; // Act - var folder = async () => await SendAsync(addFolderCommand); + var action = async () => await SendAsync(command); // Assert - await folder.Should().ThrowAsync() + await action.Should().ThrowAsync() .WithMessage("Locker does not exist."); } } \ No newline at end of file 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 80528c3c..00000000 --- a/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs +++ /dev/null @@ -1,193 +0,0 @@ -using Application.Folders.Commands.DisableFolder; -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 room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sajdbqwk", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = true, - Name = "A Random name", - NumberOfDocuments = 0, - Locker = locker - }; - - await AddAsync(folder); - var disableFolderCommand = new DisableFolderCommand() - { - FolderId = folder.Id - }; - - // Act - var disabledFolder = await SendAsync(disableFolderCommand); - - // Assert - disabledFolder.IsAvailable.Should().BeFalse(); - - // Cleanup - Remove(folder); - Remove(locker); - Remove(room); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenFolderDoesNotExist() - { - // Arrange - var disableFolderCommand = new DisableFolderCommand() - { - 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 room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room!", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sajdbqwk!", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = false, - Name = "A Random name!", - NumberOfDocuments = 0, - Locker = locker - }; - await AddAsync(folder); - var disableFolderCommand = new DisableFolderCommand() - { - 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); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenFolderHasDocuments() - { - // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room!!safqwf!!", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sawfqfw wfqwfjdbqwk!", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = true, - Name = "A Randasfwqfawfqom name!", - NumberOfDocuments = 1, - Locker = locker - }; - - var document = new Document() - { - DocumentType = "fqwkfwqbfk", - Id = Guid.NewGuid(), - Title = "wjqk ljfqwjlf qwkhjf ;qikwf", - Folder = folder - }; - - await AddAsync(document); - var disableFolderCommand = new DisableFolderCommand() - { - 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); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Commands/RemoveFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/RemoveFolderTests.cs new file mode 100644 index 00000000..1d8f091e --- /dev/null +++ b/tests/Application.Tests.Integration/Folders/Commands/RemoveFolderTests.cs @@ -0,0 +1,32 @@ +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 RemoveFolderTests : BaseClassFixture +{ + public RemoveFolderTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenFolderDoesNotExist() + { + // Arrange + var command = new RemoveFolder.Command() + { + FolderId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Folder does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Commands/UpdateFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/UpdateFolderTests.cs new file mode 100644 index 00000000..0205f6b1 --- /dev/null +++ b/tests/Application.Tests.Integration/Folders/Commands/UpdateFolderTests.cs @@ -0,0 +1,34 @@ +using Application.Common.Exceptions; +using Application.Folders.Commands; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Folders.Commands; + +public class UpdateFolderTests : BaseClassFixture +{ + public UpdateFolderTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatFolderDoesNotExist() + { + // Arrange + var command = new UpdateFolder.Command() + { + FolderId = Guid.NewGuid(), + Name = "Something else", + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Folder does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Queries/GetFolderByIdTests.cs b/tests/Application.Tests.Integration/Folders/Queries/GetFolderByIdTests.cs new file mode 100644 index 00000000..067b4d18 --- /dev/null +++ b/tests/Application.Tests.Integration/Folders/Queries/GetFolderByIdTests.cs @@ -0,0 +1,30 @@ +using Application.Folders.Queries; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Folders.Queries; + +public class GetFolderByIdTests : BaseClassFixture +{ + public GetFolderByIdTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatFolderDoesNotExist() + { + // Arrange + var query = new GetFolderById.Query() + { + FolderId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(query); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Folder does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs deleted file mode 100644 index 5cedf272..00000000 --- a/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs +++ /dev/null @@ -1,212 +0,0 @@ -using Application.Common.Exceptions; -using Application.Lockers.Commands.AddLocker; -using Bogus; -using Domain.Entities.Physical; -using Domain.Exceptions; -using FluentAssertions; -using MediatR; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Application.Tests.Integration.Lockers.Commands; - -public class AddLockerTests : BaseClassFixture -{ - public AddLockerTests(CustomApiFactory apiFactory) : base(apiFactory) - { - - } - - [Fact] - public async Task ShouldReturnLocker_WhenCreateDetailsAreValid() - { - // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var addLockerCommand = new AddLockerCommand() - { - Name = new Faker().Name.JobTitle(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room.Id, - }; - - room.NumberOfLockers += 1; - - // Act - - var locker = await SendAsync(addLockerCommand); - - // Assert - locker.Name.Should().Be(addLockerCommand.Name); - locker.Description.Should().Be(addLockerCommand.Description); - locker.Capacity.Should().Be(addLockerCommand.Capacity); - locker.NumberOfFolders.Should().Be(0); - locker.IsAvailable.Should().BeTrue(); - locker.Room.Id.Should().Be(room.Id); - locker.Room.NumberOfLockers.Should().Be(room.NumberOfLockers); - - // Cleanup - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenLockerAlreadyExistsInTheSameRoom() - { - // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var addLockerCommand = new AddLockerCommand() - { - Name = new Faker().Name.JobTitle(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room.Id, - }; - - await SendAsync(addLockerCommand); - - // Act - var action = async () => await SendAsync(addLockerCommand); - - // Assert - await action.Should().ThrowAsync().WithMessage("Locker's name already exists."); - - // Cleanup - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldReturnLocker_WhenLockersHasSameNameButInDifferentRooms() - { - // Arrange - var room1 = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room1); - - var room2 = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room2); - - var addLockerCommand = new AddLockerCommand() - { - Name = new Faker().Name.JobTitle(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room1.Id, - }; - - var addLockerCommand2 = new AddLockerCommand() - { - Name = addLockerCommand.Name, - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room2.Id, - }; - - room2.NumberOfLockers += 1; - - var locker1 = await SendAsync(addLockerCommand); - - // Act - var locker2 = await SendAsync(addLockerCommand2); - - // Assert - locker2.Name.Should().Be(addLockerCommand2.Name); - locker2.Description.Should().Be(addLockerCommand2.Description); - locker2.Capacity.Should().Be(addLockerCommand2.Capacity); - locker2.NumberOfFolders.Should().Be(0); - locker2.IsAvailable.Should().BeTrue(); - locker2.Room.Id.Should().Be(room2.Id); - locker2.Room.NumberOfLockers.Should().Be(room2.NumberOfLockers); - - // Cleanup - var room1Entity = await FindAsync(room1.Id); - var room2Entity = await FindAsync(room2.Id); - Remove(room1Entity); - Remove(room2Entity); - } - - [Fact] - public async Task ShouldThrowLimitExceededException_WhenGoingOverCapacity() - { - // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var addLockerCommand = new AddLockerCommand() - { - Name = new Faker().Name.JobTitle(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room.Id, - }; - - var addLockerCommand2 = new AddLockerCommand() - { - Name = new Faker().Name.JobTitle(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - RoomId = room.Id, - }; - - var locker = await SendAsync(addLockerCommand); - - // Act - var action = async () => await SendAsync(addLockerCommand2); - - // Assert - await action.Should().ThrowAsync().WithMessage("This room cannot accept more lockers."); - - // Cleanup - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } -} \ 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 28538e7b..00000000 --- a/tests/Application.Tests.Integration/Lockers/Commands/DisableLockerTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Application.Common.Exceptions; -using Application.Lockers.Commands.AddLocker; -using Application.Lockers.Commands.DisableLocker; -using Bogus; -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 room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var createLockerCommand = new AddLockerCommand() - { - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 2, - RoomId = room.Id, - }; - - var locker = await SendAsync(createLockerCommand); - room.NumberOfLockers += 1; - - var disableLockerCommand = new DisableLockerCommand() - { - 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 - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() - { - // Arrange - var disableLockerCommand = new DisableLockerCommand() - { - 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 room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var createLockerCommand = new AddLockerCommand() - { - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 2, - RoomId = room.Id, - }; - - var locker = await SendAsync(createLockerCommand); - var disableLockerCommand = new DisableLockerCommand() - { - 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 - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } -} \ 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 7f3a9dbb..00000000 --- a/tests/Application.Tests.Integration/Lockers/Commands/EnableLockerTests.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Application.Common.Exceptions; -using Application.Lockers.Commands.AddLocker; -using Application.Lockers.Commands.DisableLocker; -using Application.Lockers.Commands.EnableLocker; -using Bogus; -using Domain.Entities.Physical; -using Domain.Exceptions; -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 room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var createLockerCommand = new AddLockerCommand() - { - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 2, - RoomId = room.Id, - }; - - var locker = await SendAsync(createLockerCommand); - - var disableLockerCommand = new DisableLockerCommand() - { - LockerId = locker.Id, - }; - - await SendAsync(disableLockerCommand); - - // Act - - var enableLockerCommand = new EnableLockerCommand() - { - LockerId = locker.Id, - }; - - var result = await SendAsync(enableLockerCommand); - - // Assert - result.IsAvailable.Should().BeTrue(); - - // Cleanup - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() - { - // Arrange - var enableLockerCommand = new EnableLockerCommand() - { - LockerId = Guid.NewGuid(), - }; - - // Act - var action = async () => await SendAsync(enableLockerCommand); - - // Assert - await action.Should() - .ThrowAsync() - .WithMessage("Locker does not exist."); - } - - [Fact] - public async Task ShouldThrowConflictException_WhenLockerIsAlreadyEnabled() - { - // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - - await AddAsync(room); - - var createLockerCommand = new AddLockerCommand() - { - Name = new Faker().Commerce.ProductName(), - Description = new Faker().Lorem.Sentence(), - Capacity = 2, - RoomId = room.Id, - }; - - var locker = await SendAsync(createLockerCommand); - var enableLockerCommand = new EnableLockerCommand() - { - LockerId = locker.Id, - }; - - // Act - var action = async () => await SendAsync(enableLockerCommand); - - // Assert - await action.Should() - .ThrowAsync() - .WithMessage("Locker has already been enabled."); - - // Cleanup - var roomEntity = await FindAsync(room.Id); - Remove(roomEntity); - } -} diff --git a/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs new file mode 100644 index 00000000..1cbf17d9 --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Commands/RemoveLockerTests.cs @@ -0,0 +1,64 @@ +using Application.Common.Exceptions; +using Application.Lockers.Commands; +using Application.Rooms.Commands; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Commands; + +public class RemoveLockerTests : BaseClassFixture +{ + public RemoveLockerTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowConflictException_WhenRoomStillHasDocuments() + { + // 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 command = new RemoveLocker.Command() + { + LockerId = locker.Id, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Locker cannot be removed because it contains documents."); + + // Cleanup + Remove(documents.First()); + Remove(folder); + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenLockerDoesNotExist() + { + // Arrange + var command = new RemoveLocker.Command() + { + LockerId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Locker does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs new file mode 100644 index 00000000..ea29908c --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Commands/UpdateLockerTests.cs @@ -0,0 +1,101 @@ +using Application.Common.Exceptions; +using Application.Lockers.Commands; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Commands; + +public class UpdateLockerTests : BaseClassFixture +{ + public UpdateLockerTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatLockerDoesNotExist() + { + // Arrange + var command = new UpdateLocker.Command() + { + LockerId = Guid.NewGuid(), + Name = "Something else", + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("Locker does not exist."); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewLockerNameHasAlreadyExistedInThatRoom() + { + // Arrange + var department = CreateDepartment(); + var duplicateNameLocker = CreateLocker(); + var locker = CreateLocker(); + var room = CreateRoom(department, duplicateNameLocker, locker); + await AddAsync(room); + + var command = new UpdateLocker.Command() + { + LockerId = locker.Id, + Name = duplicateNameLocker.Name, + Capacity = 6, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("New locker name already exists."); + + // Cleanup + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewCapacityIsLessThanCurrentNumberOfFolders() + { + // Arrange + var department = CreateDepartment(); + var folder1 = CreateFolder(); + var folder2 = CreateFolder(); + var locker = CreateLocker(folder1, folder2); + locker.Capacity = 3; + var room = CreateRoom(department, locker); + await AddAsync(room); + + var command = new UpdateLocker.Command() + { + LockerId = locker.Id, + Name = "Something else", + Capacity = 1, + Description = "ehehe", + }; + + // Act + var result = async () => await SendAsync(command); + + // Assert + await result.Should().ThrowAsync() + .WithMessage("New capacity cannot be less than current number of folders."); + + // Cleanup + Remove(folder1); + Remove(folder2); + Remove(locker); + Remove(room); + Remove(await FindAsync(department.Id)); + } + +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Queries/GetLockerByIdTests.cs b/tests/Application.Tests.Integration/Lockers/Queries/GetLockerByIdTests.cs new file mode 100644 index 00000000..427b431b --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Queries/GetLockerByIdTests.cs @@ -0,0 +1,30 @@ +using Application.Lockers.Queries; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Queries; + +public class GetLockerByIdTests : BaseClassFixture +{ + public GetLockerByIdTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatLockerDoesNotExist() + { + // Arrange + var query = new GetLockerById.Query() + { + LockerId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(query); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Locker does not exist."); + } +} \ No newline at end of file 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 8b83f4da..00000000 --- a/tests/Application.Tests.Integration/Rooms/Commands/DisableRoomTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Application.Common.Exceptions; -using Application.Helpers; -using Application.Lockers.Commands.AddLocker; -using Application.Rooms.Commands.DisableRoom; -using Bogus; -using Domain.Entities; -using Domain.Entities.Physical; -using FluentAssertions; -using NodaTime; -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 folder = CreateFolder(); - var locker = CreateLocker(folder); - var room = CreateRoom(locker); - await AddAsync(room); - - var disableRoomCommand = new DisableRoomCommand() - { - 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); - } - - [Fact] - public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() - { - // Arrange - var disableRoomCommand = new DisableRoomCommand() - { - 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 documents = CreateNDocuments(1); - var folder = CreateFolder(documents); - var locker = CreateLocker(folder); - var room = CreateRoom(locker); - - await AddAsync(room); - - var disableRoomCommand = new DisableRoomCommand() - { - 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); - } - - [Fact] - public async Task ShouldThrowInvalidOperationException_WhenRoomIsNotAvailable() - { - // Arrange - var room = CreateRoom(); - room.IsAvailable = false; - await AddAsync(room); - - var command = new DisableRoomCommand() - { - RoomId = room.Id - }; - - // Act - var action = async () => await SendAsync(command); - - // Assert - await action.Should().ThrowAsync() - .WithMessage("Room have already been disabled."); - - // Cleanup - Remove(room); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Commands/RemoveRoomTests.cs b/tests/Application.Tests.Integration/Rooms/Commands/RemoveRoomTests.cs index 70954ce7..b30a5112 100644 --- a/tests/Application.Tests.Integration/Rooms/Commands/RemoveRoomTests.cs +++ b/tests/Application.Tests.Integration/Rooms/Commands/RemoveRoomTests.cs @@ -1,5 +1,6 @@ -using Application.Rooms.Commands.RemoveRoom; -using Bogus; +using Application.Common.Exceptions; +using Application.Rooms.Commands; +using Domain.Entities; using Domain.Entities.Physical; using FluentAssertions; using Xunit; @@ -11,38 +12,19 @@ public class RemoveRoomTests : BaseClassFixture public RemoveRoomTests(CustomApiFactory apiFactory) : base(apiFactory) { } - - [Fact] - public async Task ShouldRemoveRoom_WhenRoomHasNoDocuments() - { - // Arrange - var room = CreateRoom(); - await Add(room); - - var command = new RemoveRoomCommand() - { - RoomId = room.Id - }; - // Act - var result = await SendAsync(command); - - // Assert - var deletedRoom = await FindAsync(room.Id); - result.IsAvailable.Should().BeFalse(); - deletedRoom.Should().BeNull(); - } - + [Fact] public async Task ShouldThrowInvalidOperationException_WhenRoomHaveDocuments() { // Arrange + var department = CreateDepartment(); var documents = CreateNDocuments(1); var folder = CreateFolder(documents); var locker = CreateLocker(folder); - var room = CreateRoom(locker); + var room = CreateRoom(department, locker); await AddAsync(room); - var command = new RemoveRoomCommand() + var command = new RemoveRoom.Command() { RoomId = room.Id }; @@ -51,21 +33,22 @@ public async Task ShouldThrowInvalidOperationException_WhenRoomHaveDocuments() var action = async () => await SendAsync(command); // Assert - await action.Should().ThrowAsync() - .WithMessage("Room cannot be removed because it contains documents."); + await action.Should().ThrowAsync() + .WithMessage("Room cannot be removed because it contains something."); // Cleanup Remove(documents.First()); Remove(folder); Remove(locker); Remove(room); + Remove(await FindAsync(department.Id)); } [Fact] public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() { // Arrange - var command = new RemoveRoomCommand() + var command = new RemoveRoom.Command() { RoomId = Guid.NewGuid() }; diff --git a/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs b/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs new file mode 100644 index 00000000..79409a15 --- /dev/null +++ b/tests/Application.Tests.Integration/Rooms/Commands/UpdateRoomTests.cs @@ -0,0 +1,99 @@ +using Application.Common.Exceptions; +using Application.Rooms.Commands; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Rooms.Commands; + +public class UpdateRoomTests : BaseClassFixture +{ + public UpdateRoomTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() + { + // Act + var command = new UpdateRoom.Command() + { + RoomId = Guid.NewGuid(), + Name = "Something else", + Description = "Description else", + Capacity = 6, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Room does not exist."); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewCapacityIsLowerThanNumberOfCurrentLockers() + { + // Act + var department = CreateDepartment(); + var locker1 = CreateLocker(); + var locker2 = CreateLocker(); + var room = CreateRoom(department, locker1, locker2); + await AddAsync(room); + + var command = new UpdateRoom.Command() + { + RoomId = room.Id, + Name = "Something else", + Description = "Description else", + Capacity = 1, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("New capacity cannot be less than current number of lockers."); + + // Cleanup + Remove(locker1); + Remove(locker2); + Remove(room); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowConflictException_WhenNewNameHasAlreadyExisted() + { + // Act + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + var existedNameRoom = CreateRoom(department1); + var room = CreateRoom(department2); + await AddAsync(existedNameRoom); + await AddAsync(room); + + var command = new UpdateRoom.Command() + { + RoomId = room.Id, + Name = existedNameRoom.Name, + Description = "Description else", + Capacity = 1, + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Name has already exists."); + + // Cleanup + Remove(existedNameRoom); + Remove(room); + Remove(await FindAsync(department1.Id)); + Remove(await FindAsync(department2.Id)); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Queries/GetEmptyContainersPaginatedTests.cs b/tests/Application.Tests.Integration/Rooms/Queries/GetEmptyContainersPaginatedTests.cs index c0c001b1..05ca1bf0 100644 --- a/tests/Application.Tests.Integration/Rooms/Queries/GetEmptyContainersPaginatedTests.cs +++ b/tests/Application.Tests.Integration/Rooms/Queries/GetEmptyContainersPaginatedTests.cs @@ -1,11 +1,9 @@ -using Application.Lockers.Commands.AddLocker; -using Application.Rooms.Queries.GetEmptyContainersPaginated; +using Application.Rooms.Queries; using Bogus; using Domain.Entities.Physical; using FluentAssertions; using Infrastructure.Persistence; using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualBasic; using Xunit; namespace Application.Tests.Integration.Rooms.Queries; @@ -20,8 +18,19 @@ public GetEmptyContainersPaginatedTests(CustomApiFactory apiFactory) : base(apiF public async Task ShouldReturnLockersWithEmptyFolders() { // Arrange - var room = await SetupTestEntities(); - var query = new GetEmptyContainersPaginatedQuery() + var department = CreateDepartment(); + var folder1 = CreateFolder(); + var folder2 = CreateFolder(); + var folder3 = CreateFolder(); + folder3.IsAvailable = false; + var locker1 = CreateLocker(folder1, folder2); + locker1.Capacity = 2; + var locker2 = CreateLocker(folder3); + locker2.Capacity = 2; + var room = CreateRoom(department, locker1, locker2); + await AddAsync(room); + + var query = new GetEmptyContainersPaginated.Query() { Page = 1, Size = 2, @@ -36,23 +45,24 @@ public async Task ShouldReturnLockersWithEmptyFolders() result.Items.First().Id.Should().Be(room.Lockers.ElementAt(0).Id); result.Items.First().Name.Should().Be(room.Lockers.ElementAt(0).Name); result.Items.First().Description.Should().Be(room.Lockers.ElementAt(0).Description); - result.Items.First().NumberOfFreeFolders.Should().Be(1); - result.Items.First().Capacity.Should().Be(4); - result.Items.First().NumberOfFolders.Should().Be(2); - result.Items.First().Folders.First().Id.Should().Be(room.Lockers.ElementAt(0).Folders.First().Id); - result.Items.First().Folders.First().Name.Should().Be(room.Lockers.ElementAt(0).Folders.First().Name); - result.Items.First().Folders.First().Description.Should().Be(room.Lockers.ElementAt(0).Folders.First().Description); - result.Items.First().Folders.First().Slot.Should().Be(3); + result.Items.First().NumberOfFreeFolders.Should().Be(2); + result.Items.First().Capacity.Should().Be(2); // Cleanup - await CleanupTestEntities(room); + Remove(folder1); + Remove(folder2); + Remove(folder3); + Remove(locker1); + Remove(locker2); + Remove(room); + Remove(department); } [Fact] public async Task ShouldThrowNotFound_WhenRoomDoesNotExist() { // Arrange - var query = new GetEmptyContainersPaginatedQuery() + var query = new GetEmptyContainersPaginated.Query() { Page = 1, Size = 2, @@ -65,92 +75,4 @@ public async Task ShouldThrowNotFound_WhenRoomDoesNotExist() // Assert await action.Should().ThrowAsync("Room does not exist"); } - - private async Task SetupTestEntities() - { - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.FirstName, - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 2, - }; - - var locker1 = new Locker() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.LastName, - Room = room, - Capacity = 4, - IsAvailable = true, - NumberOfFolders = 2 - }; - - var locker2 = new Locker() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.UserName, - Room = room, - Capacity = 4, - IsAvailable = false, - NumberOfFolders = 1 - }; - - var folder1 = new Folder() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.FirstName, - Locker = locker1, - IsAvailable = true, - Capacity = 3, - NumberOfDocuments = 0, - }; - - var folder2 = new Folder() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.LastName, - Locker = locker1, - IsAvailable = true, - Capacity = 2, - NumberOfDocuments = 3 - }; - - var folder3 = new Folder() - { - Id = Guid.NewGuid(), - Name = new Faker().Person.UserName, - Locker = locker2, - IsAvailable = false, - Capacity = 3, - NumberOfDocuments = 1 - }; - - using var scope = _scopeFactory.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - - await context.Rooms.AddAsync(room); - await context.Lockers.AddAsync(locker1); - await context.Lockers.AddAsync(locker2); - await context.Folders.AddAsync(folder1); - await context.Folders.AddAsync(folder2); - await context.Folders.AddAsync(folder3); - - await context.SaveChangesAsync(); - - return room; - } - - private async Task CleanupTestEntities(Room room) - { - using var scope = _scopeFactory.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - - context.RemoveRange(room.Lockers.SelectMany(l => l.Folders)); - context.RemoveRange(room.Lockers); - context.Remove(room); - - await context.SaveChangesAsync(); - } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Queries/GetRoomByIdTests.cs b/tests/Application.Tests.Integration/Rooms/Queries/GetRoomByIdTests.cs new file mode 100644 index 00000000..785237a2 --- /dev/null +++ b/tests/Application.Tests.Integration/Rooms/Queries/GetRoomByIdTests.cs @@ -0,0 +1,30 @@ +using Application.Rooms.Queries; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Rooms.Queries; + +public class GetRoomByIdTests : BaseClassFixture +{ + public GetRoomByIdTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatRoomDoesNotExist() + { + // Arrange + var query = new GetRoomById.Query() + { + RoomId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(query); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Room does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffFromRoomTests.cs b/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffFromRoomTests.cs new file mode 100644 index 00000000..eddd28d2 --- /dev/null +++ b/tests/Application.Tests.Integration/Staffs/Commands/RemoveStaffFromRoomTests.cs @@ -0,0 +1,41 @@ +using Application.Common.Exceptions; +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 RemoveStaffFromRoomTests : BaseClassFixture +{ + public RemoveStaffFromRoomTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowConflictException_WhenStaffIsNotAssignedToARoom() + { + // Arrange + var user = CreateUser(IdentityData.Roles.Admin,"123123"); + var staff = CreateStaff(user, null); + await AddAsync(staff); + + var command = new RemoveStaffFromRoom.Command() + { + StaffId = staff.Id + }; + + // Act + var action = async () => await SendAsync(command); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Staff is not assigned to a room."); + + // Cleanup + Remove(staff); + Remove(await FindAsync(user.Id)); + } +} \ 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 new file mode 100644 index 00000000..99cc4ef6 --- /dev/null +++ b/tests/Application.Tests.Integration/Staffs/Queries/GetAllStaffsPaginatedTests.cs @@ -0,0 +1,75 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using Application.Staffs.Queries; +using AutoMapper; +using Domain.Entities; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Staffs.Queries; + +public class GetAllStaffsPaginatedTests : BaseClassFixture +{ + private readonly IMapper _mapper; + public GetAllStaffsPaginatedTests(CustomApiFactory apiFactory) : base(apiFactory) + { + var configuration = new MapperConfiguration(config => config.AddProfile()); + _mapper = configuration.CreateMapper(); + } + + [Fact] + public async Task ShouldReturnStaffs() + { + // Arrange + var user1 = CreateUser(IdentityData.Roles.Staff, "123123"); + var user2 = CreateUser(IdentityData.Roles.Staff, "435455"); + var staff1 = CreateStaff(user1, null); + var staff2 = CreateStaff(user2, null); + await AddAsync(staff1); + await AddAsync(staff2); + + var query = new GetAllStaffsPaginated.Query(); + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(2); + + // Cleanup + Remove(staff2); + Remove(staff1); + Remove(await FindAsync(user2.Id)); + Remove(await FindAsync(user1.Id)); + } + + [Fact] + public async Task ShouldReturnASpecificStaff() + { + // Arrange + var user1 = CreateUser(IdentityData.Roles.Staff, "123123"); + var user2 = CreateUser(IdentityData.Roles.Staff, "435455"); + var staff1 = CreateStaff(user1, null); + var staff2 = CreateStaff(user2, null); + await AddAsync(staff1); + await AddAsync(staff2); + + var query = new GetAllStaffsPaginated.Query() + { + SearchTerm = staff1.User.Username + }; + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(1); + + // Cleanup + Remove(staff2); + Remove(staff1); + Remove(await FindAsync(user2.Id)); + Remove(await FindAsync(user1.Id)); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByIdTests.cs b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByIdTests.cs new file mode 100644 index 00000000..ac11a39a --- /dev/null +++ b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByIdTests.cs @@ -0,0 +1,61 @@ +using Application.Identity; +using Application.Staffs.Queries; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Staffs.Queries; + +public class GetStaffByIdTests : BaseClassFixture +{ + public GetStaffByIdTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldReturnStaff_WhenIdIsValid() + { + // Arrange + var department = CreateDepartment(); + var user = CreateUser(IdentityData.Roles.Staff, "123123"); + var room = CreateRoom(department); + var staff = CreateStaff(user, room); + await AddAsync(staff); + + var query = new GetStaffById.Query() + { + StaffId = staff.Id + }; + + // Act + var result = await SendAsync(query); + + // Assert + result.User.Id.Should().Be(staff.Id); + + // Cleanup + Remove(staff); + Remove(await FindAsync(room.Id)); + Remove(await FindAsync(user.Id)); + Remove(await FindAsync(department.Id)); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenStaffDoesNotExist() + { + // Arrange + var query = new GetStaffById.Query() + { + StaffId = Guid.NewGuid() + }; + + // Act + var action = async () => await SendAsync(query); + + // 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/GetStaffByRoomTests.cs b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs new file mode 100644 index 00000000..f2ac064b --- /dev/null +++ b/tests/Application.Tests.Integration/Staffs/Queries/GetStaffByRoomTests.cs @@ -0,0 +1,40 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using Application.Staffs.Queries; +using Application.Users.Queries; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Staffs.Queries; + +public class GetStaffByRoomTests : BaseClassFixture +{ + private readonly IMapper _mapper; + + public GetStaffByRoomTests(CustomApiFactory apiFactory) : base(apiFactory) + { + var configuration = new MapperConfiguration(config => config.AddProfile()); + _mapper = configuration.CreateMapper(); + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenRoomDoesNotExist() + { + // Arrange + var query = new GetStaffByRoomId.Query() + { + RoomId = Guid.NewGuid() + }; + + // Act + var action = async () => await SendAsync(query); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("Room does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Users/Commands/CreateUserTests.cs b/tests/Application.Tests.Integration/Users/Commands/CreateUserTests.cs deleted file mode 100644 index 53c1fbdb..00000000 --- a/tests/Application.Tests.Integration/Users/Commands/CreateUserTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Application.Users.Commands.CreateUser; -using Bogus; -using Domain.Entities; -using FluentAssertions; -using Xunit; - -namespace Application.Tests.Integration.Users.Commands; - -public class CreateUserTests : BaseClassFixture -{ - private readonly Faker _userGenerator = new Faker() - .RuleFor(x => x.Username, faker => faker.Person.UserName) - .RuleFor(x => x.Email, faker => faker.Person.Email) - .RuleFor(x => x.FirstName, faker => faker.Person.FirstName) - .RuleFor(x => x.LastName, faker => faker.Person.LastName) - .RuleFor(x => x.Password, faker => faker.Random.String()) - .RuleFor(x => x.Role, faker => faker.Random.Word()) - .RuleFor(x => x.Position, faker => faker.Random.Word()); - public CreateUserTests(CustomApiFactory apiFactory) : base(apiFactory) - { - } - - [Fact] - public async Task ShouldCreateUser_WhenCreateDetailsAreValid() - { - // Arrange - var department = new Department() - { - Id = Guid.NewGuid(), - Name = new Faker().Commerce.Department() - }; - await AddAsync(department); - var createUserCommand = _userGenerator.Generate(); - createUserCommand = createUserCommand with - { - DepartmentId = department.Id - }; - - // Act - var user = await SendAsync(createUserCommand); - - // Assert - user.Username.Should().Be(createUserCommand.Username); - user.FirstName.Should().Be(createUserCommand.FirstName); - user.LastName.Should().Be(createUserCommand.LastName); - user.Department.Should().BeEquivalentTo(new { department.Id, department.Name }); - user.Email.Should().Be(createUserCommand.Email); - user.Role.Should().Be(createUserCommand.Role); - user.Position.Should().Be(createUserCommand.Position); - user.IsActive.Should().Be(true); - user.IsActivated.Should().Be(false); - - // Clean up - var userEntity = await FindAsync(user.Id); - Remove(userEntity); - Remove(department); - } -} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Users/Queries/GetAllUsersPaginatedTests.cs b/tests/Application.Tests.Integration/Users/Queries/GetAllUsersPaginatedTests.cs new file mode 100644 index 00000000..0133c725 --- /dev/null +++ b/tests/Application.Tests.Integration/Users/Queries/GetAllUsersPaginatedTests.cs @@ -0,0 +1,43 @@ +using Application.Common.Mappings; +using Application.Identity; +using Application.Users.Queries; +using AutoMapper; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Users.Queries; + +public class GetAllUsersPaginatedTests : BaseClassFixture +{ + private readonly IMapper _mapper; + public GetAllUsersPaginatedTests(CustomApiFactory apiFactory) : base(apiFactory) + { + var configuration = new MapperConfiguration(config => config.AddProfile()); + + _mapper = configuration.CreateMapper(); + } + + [Fact] + public async Task ShouldReturnUsers() + { + // Arrange + var user1 = CreateUser(IdentityData.Roles.Admin, "admin"); + var user2 = CreateUser(IdentityData.Roles.Employee, "employee"); + await AddAsync(user1); + await AddAsync(user2); + + var query = new GetAllUsersPaginated.Query(); + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(1); + result.Items.Should().ContainEquivalentOf(_mapper.Map(user2), config => config.Excluding(x => x.Created)); + result.Items.Should().NotContainEquivalentOf(_mapper.Map(user1), config => config.Excluding(x => x.Created)); + + // Cleanup + Remove(user1); + Remove(user2); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Users/Queries/GetUserByIdTests.cs b/tests/Application.Tests.Integration/Users/Queries/GetUserByIdTests.cs new file mode 100644 index 00000000..1a603568 --- /dev/null +++ b/tests/Application.Tests.Integration/Users/Queries/GetUserByIdTests.cs @@ -0,0 +1,30 @@ +using Application.Identity; +using Application.Users.Queries; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Users.Queries; + +public class GetUserByIdTests : BaseClassFixture +{ + public GetUserByIdTests(CustomApiFactory apiFactory) : base(apiFactory) + { + } + + [Fact] + public async Task ShouldThrowKeyNotFoundException_WhenThatUserDoesNotExist() + { + // Arrange + var query = new GetUserById.Query() + { + UserId = Guid.NewGuid(), + }; + + // Act + var action = async () => await SendAsync(query); + + // Assert + await action.Should().ThrowAsync() + .WithMessage("User does not exist."); + } +} \ No newline at end of file diff --git a/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs b/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs index 36b57359..2d635750 100644 --- a/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs +++ b/tests/Application.Tests.Unit/Common/Mappings/MappingTests.cs @@ -1,12 +1,13 @@ using System.Runtime.Serialization; 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.Documents.Queries.GetAllDocumentsPaginated; -using Application.Rooms.Queries.GetEmptyContainersPaginated; using Application.Users.Queries; using AutoMapper; using Domain.Entities; +using Domain.Entities.Digital; using Domain.Entities.Physical; using Xunit; @@ -45,6 +46,11 @@ public void ShouldHaveValidConfiguration() [InlineData(typeof(Document), typeof(DocumentItemDto))] [InlineData(typeof(Borrow), typeof(BorrowDto))] [InlineData(typeof(RefreshToken), typeof(RefreshTokenDto))] + [InlineData(typeof(FileEntity), typeof(FileDto))] + [InlineData(typeof(Entry), typeof(EntryDto))] + [InlineData(typeof(User), typeof(IssuerDto))] + [InlineData(typeof(Document), typeof(IssuedDocumentDto))] + [InlineData(typeof(ImportRequest), typeof(ImportRequestDto))] public void ShouldSupportMappingFromSourceToDestination(Type source, Type destination) { // Arrange diff --git a/tests/Application.Tests.Unit/Helpers/SecurityUtilTests.cs b/tests/Application.Tests.Unit/Helpers/SecurityUtilTests.cs new file mode 100644 index 00000000..259638f7 --- /dev/null +++ b/tests/Application.Tests.Unit/Helpers/SecurityUtilTests.cs @@ -0,0 +1,27 @@ +using Application.Helpers; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Unit.Helpers; + +public class SecurityUtilTests +{ + public SecurityUtilTests() + { + } + + [Fact] + public void ShouldReturnHash_WhenHashPasswordWithSaltAndPepper() + { + // Arrange + string salt = "dwnqjkdqwW4q"; + string input = "ThizIsAveRyl0000GandS3cur4dP@ssWord"; + string pepper = "Some secret here"; + string expectedHash = "27745bdd5e09aae12213a1ea7a3f9056b7de257050370d6cfa1c2d1b954de335"; + // Act + string password = input.HashPasswordWith(salt, pepper); + + // Assert + password.Should().Be(expectedHash); + } +} \ No newline at end of file