From ab43b4c4261538300def9fefc5766f444e66d701 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 15:55:50 +0800 Subject: [PATCH 01/10] fix: category --- .../Migrations/20250913035915_SQLServer.cs | 413 -------------- .../20250927053802_UpdateSeededDates.cs | 257 --------- .../{Adapters => }/Blazor/BlazorApp.razor | 0 .../Common/{Adapters => }/Blazor/Routes.razor | 0 .../{Adapters => }/Data/AppDbContext.cs | 91 ++- .../20250913035915_SQLServer.Designer.cs | 4 +- .../Migrations/20250913035915_SQLServer.cs | 500 ++++++++++++++++ ...250927053802_UpdateSeededDates.Designer.cs | 3 +- .../20250927053802_UpdateSeededDates.cs | 407 +++++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 4 +- .../Common/Extensions/MapperExtension.cs | 4 +- .../Extensions/ServiceContainerExtensions.cs | 2 +- .../Middlewares/GlobalExceptionHandler.cs | 4 +- .../{Common => }/Domains/Entities/Account.cs | 2 +- .../{Common => }/Domains/Entities/Booking.cs | 8 +- .../{Common => }/Domains/Entities/Category.cs | 2 +- .../Domains/Entities/Gathering.cs | 6 +- .../Entities/GatheringCategoryDetail.cs | 2 +- .../ExternalLoginProviderException.cs | 2 +- .../Domains/Interfaces/IAccountsService.cs | 4 +- .../Domains/Interfaces/IBookingService.cs | 11 +- .../Domains/Interfaces/ICategoryService.cs | 6 +- .../Domains/Interfaces/IEmailerAdapter.cs | 2 +- .../Domains/Interfaces/IGatheringService.cs | 6 +- .../Domains/Interfaces/IMediaRenderer.cs | 2 +- .../Interfaces/IObjectStorageService.cs | 6 +- .../{Common => }/Domains/Models/AccountDto.cs | 2 +- .../Domains/Models/BookingReqDto.cs | 2 +- .../Models/GatheringCategoryDetailDto.cs | 2 +- .../Domains/Models/GatheringReqDto.cs | 2 +- .../{Common => }/Domains/Models/PageResult.cs | 2 +- .../{Common => }/Domains/Models/Settings.cs | 2 +- src/Evently.Server/Evently.Server.csproj | 8 +- .../Accounts/Controllers/AccountController.cs | 8 +- .../Services/AccountAuthorizationHandler.cs | 2 +- .../Accounts/Services/AccountService.cs | 6 +- .../Accounts/Services/AccountValidator.cs | 2 +- .../Controllers/BookingsController.cs | 14 +- .../Bookings/Services/BookingService.cs | 21 +- .../Bookings/Services/BookingValidator.cs | 2 +- .../Controllers/CategoriesController.cs | 6 +- .../Categories/Services/CategoryService.cs | 10 +- .../Features/Emails/Services/EmailAdapter.cs | 4 +- .../Emails/Services/EmailBackgroundService.cs | 7 +- .../Features/Emails/Services/MediaRenderer.cs | 4 +- .../Features/Emails/Views/Ticket.razor | 27 +- .../Files/Controllers/FilesController.cs | 6 +- .../Files/Services/ObjectStorageService.cs | 7 +- .../Controllers/GatheringsController.cs | 19 +- .../Gatherings/Services/GatheringService.cs | 10 +- .../Gatherings/Services/GatheringValidator.cs | 2 +- src/Evently.Server/Program.cs | 11 +- .../{lib => }/domains/entities/entities.ts | 0 .../src/{lib => }/domains/entities/index.ts | 0 .../src/{lib => }/domains/interfaces/index.ts | 0 .../domains/interfaces/page-result.ts | 0 .../domains/interfaces/route-context.ts | 0 .../src/{lib => }/domains/models/index.ts | 0 .../{lib => }/domains/models/toast-content.ts | 0 .../{lib => }/domains/models/upsert-dtos.ts | 0 .../src/lib/components/-card.test.tsx | 2 +- .../src/lib/components/card.tsx | 2 +- .../lib/components/test-component-wrapper.tsx | 4 +- .../src/lib/services/auth-service.ts | 2 +- .../src/lib/services/booking-service.ts | 6 +- .../src/lib/services/category-service.ts | 2 +- .../lib/services/gathering-service.mock.ts | 6 +- .../src/lib/services/gathering-service.ts | 6 +- src/evently.client/src/routeTree.gen.ts | 537 +++++++++--------- src/evently.client/src/routes/__root.tsx | 4 +- .../bookings/(auth)/attending/index.tsx | 2 +- .../-components/bookings-table.tsx | 2 +- .../$gatheringId/-components/jumbotron.tsx | 2 +- .../hosting/$gatheringId/dashboard.index.tsx | 4 +- .../hosting/$gatheringId/dashboard.scan.tsx | 4 +- .../routes/bookings/(auth)/hosting/index.tsx | 2 +- .../gatherings/$gatheringId/(auth).update.tsx | 4 +- .../$gatheringId/-components/jumbotron.tsx | 2 +- .../$gatheringId/-components/qr-dialog.tsx | 2 +- .../routes/gatherings/$gatheringId/index.tsx | 4 +- .../gatherings/-components/filter-bar.tsx | 2 +- .../gatherings/-components/gathering-form.tsx | 4 +- .../-services/use-gathering-form.ts | 2 +- .../Common/Extensions/MapperExtensionTests.cs | 6 +- .../Bookings/Services/BookingServiceTests.cs | 7 +- .../Services/GatheringServiceTests.cs | 7 +- 86 files changed, 1437 insertions(+), 1126 deletions(-) delete mode 100644 src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.cs delete mode 100644 src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.cs rename src/Evently.Server/Common/{Adapters => }/Blazor/BlazorApp.razor (100%) rename src/Evently.Server/Common/{Adapters => }/Blazor/Routes.razor (100%) rename src/Evently.Server/Common/{Adapters => }/Data/AppDbContext.cs (87%) rename src/Evently.Server/Common/{Adapters => }/Data/Migrations/20250913035915_SQLServer.Designer.cs (99%) create mode 100644 src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs rename src/Evently.Server/Common/{Adapters => }/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs (99%) create mode 100644 src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs rename src/Evently.Server/Common/{Adapters => }/Data/Migrations/AppDbContextModelSnapshot.cs (99%) rename src/Evently.Server/{Common => }/Domains/Entities/Account.cs (90%) rename src/Evently.Server/{Common => }/Domains/Entities/Booking.cs (86%) rename src/Evently.Server/{Common => }/Domains/Entities/Category.cs (90%) rename src/Evently.Server/{Common => }/Domains/Entities/Gathering.cs (90%) rename src/Evently.Server/{Common => }/Domains/Entities/GatheringCategoryDetail.cs (90%) rename src/Evently.Server/{Common => }/Domains/Exceptions/ExternalLoginProviderException.cs (74%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IAccountsService.cs (69%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IBookingService.cs (69%) rename src/Evently.Server/{Common => }/Domains/Interfaces/ICategoryService.cs (53%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IEmailerAdapter.cs (82%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IGatheringService.cs (80%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IMediaRenderer.cs (88%) rename src/Evently.Server/{Common => }/Domains/Interfaces/IObjectStorageService.cs (84%) rename src/Evently.Server/{Common => }/Domains/Models/AccountDto.cs (74%) rename src/Evently.Server/{Common => }/Domains/Models/BookingReqDto.cs (82%) rename src/Evently.Server/{Common => }/Domains/Models/GatheringCategoryDetailDto.cs (73%) rename src/Evently.Server/{Common => }/Domains/Models/GatheringReqDto.cs (86%) rename src/Evently.Server/{Common => }/Domains/Models/PageResult.cs (71%) rename src/Evently.Server/{Common => }/Domains/Models/Settings.cs (96%) rename src/evently.client/src/{lib => }/domains/entities/entities.ts (100%) rename src/evently.client/src/{lib => }/domains/entities/index.ts (100%) rename src/evently.client/src/{lib => }/domains/interfaces/index.ts (100%) rename src/evently.client/src/{lib => }/domains/interfaces/page-result.ts (100%) rename src/evently.client/src/{lib => }/domains/interfaces/route-context.ts (100%) rename src/evently.client/src/{lib => }/domains/models/index.ts (100%) rename src/evently.client/src/{lib => }/domains/models/toast-content.ts (100%) rename src/evently.client/src/{lib => }/domains/models/upsert-dtos.ts (100%) diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.cs b/src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.cs deleted file mode 100644 index 710a20a..0000000 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.cs +++ /dev/null @@ -1,413 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Evently.Server.Common.Adapters.Data.Migrations -{ - /// - public partial class SQLServer : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Categories", - columns: table => new - { - CategoryId = table.Column(type: "bigint", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - CategoryName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Approved = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Categories", x => x.CategoryId); - }); - - migrationBuilder.CreateTable( - name: "Gatherings", - columns: table => new - { - GatheringId = table.Column(type: "bigint", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Description = table.Column(type: "nvarchar(max)", maxLength: 10000, nullable: false), - Start = table.Column(type: "datetimeoffset", nullable: false), - End = table.Column(type: "datetimeoffset", nullable: false), - Location = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CoverSrc = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - OrganiserId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Gatherings", x => x.GatheringId); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Bookings", - columns: table => new - { - BookingId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - AttendeeId = table.Column(type: "nvarchar(450)", nullable: false), - GatheringId = table.Column(type: "bigint", nullable: false), - CreationDateTime = table.Column(type: "datetimeoffset", nullable: false), - CheckInDateTime = table.Column(type: "datetimeoffset", nullable: true), - CheckoutDateTime = table.Column(type: "datetimeoffset", nullable: true), - CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Bookings", x => x.BookingId); - table.ForeignKey( - name: "FK_Bookings_AspNetUsers_AttendeeId", - column: x => x.AttendeeId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Bookings_Gatherings_GatheringId", - column: x => x.GatheringId, - principalTable: "Gatherings", - principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "GatheringCategoryDetails", - columns: table => new - { - GatheringId = table.Column(type: "bigint", nullable: false), - CategoryId = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_GatheringCategoryDetails", x => new { x.GatheringId, x.CategoryId }); - table.ForeignKey( - name: "FK_GatheringCategoryDetails_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "CategoryId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_GatheringCategoryDetails_Gatherings_GatheringId", - column: x => x.GatheringId, - principalTable: "Gatherings", - principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "AspNetUsers", - columns: new[] { "Id", "AccessFailedCount", "ConcurrencyStamp", "Email", "EmailConfirmed", "LockoutEnabled", "LockoutEnd", "Name", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", "PhoneNumberConfirmed", "SecurityStamp", "TwoFactorEnabled", "UserName" }, - values: new object[,] - { - { "empty-user-12345", 0, "EMPTY-CONCURRENCY-STAMP-12345", "empty@example.com", false, true, null, "Empty User", "EMPTY@EXAMPLE.COM", "EMPTY_USER", null, null, false, "EMPTY-SECURITY-STAMP-12345", false, "empty_user" }, - { "guest-user-22222", 0, "EMPTY-CONCURRENCY-STAMP-12345", "guest@example.com", false, true, null, "Guest User", "GUEST@EXAMPLE.COM", "GUEST_USER_2", null, null, false, "EMPTY-SECURITY-STAMP-12345", false, "guest_user2" } - }); - - migrationBuilder.InsertData( - table: "Categories", - columns: new[] { "CategoryId", "Approved", "CategoryName" }, - values: new object[,] - { - { 1L, false, "Information Technology" }, - { 2L, false, "Business & Networking" }, - { 3L, false, "Arts & Culture" } - }); - - migrationBuilder.InsertData( - table: "Gatherings", - columns: new[] { "GatheringId", "CancellationDateTime", "CoverSrc", "Description", "End", "Location", "Name", "OrganiserId", "Start" }, - values: new object[,] - { - { 1L, null, "", "A comprehensive summit exploring the latest in AI and machine learning", new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Marina Bay Sands Convention Centre, Singapore", "Tech Innovation Summit", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 2L, null, "", "Connect with fellow entrepreneurs and investors", new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Clarke Quay Central, Singapore", "Startup Networking Night", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 3L, null, "", "Showcasing contemporary digital art from emerging artists", new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "National Gallery Singapore", "Digital Art Exhibition", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 4L, null, "", "Learn modern web development techniques and best practices", new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Singapore Science Centre", "Web Development Workshop", "guest-user-22222", new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 5L, null, "", "Advanced strategies for scaling your business", new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Raffles City Convention Centre, Singapore", "Business Strategy Seminar", "guest-user-22222", new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 6L, null, "", "Professional photography techniques and portfolio building", new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Gardens by the Bay, Singapore", "Photography Masterclass", "guest-user-22222", new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 7L, null, "", "Intensive bootcamp covering iOS and Android development", new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "NUS School of Computing, Singapore", "Mobile App Development Bootcamp", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 8L, null, "", "Learn about personal finance and investment strategies", new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Suntec Singapore Convention Centre", "Investment & Finance Forum", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 9L, null, "", "Explore storytelling techniques and creative expression", new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Esplanade Theatres, Singapore", "Creative Writing Workshop", "guest-user-22222", new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 10L, null, "", "Latest trends in cloud architecture and DevOps", new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Singapore EXPO", "Cloud Computing Conference", "empty-user-12345", new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 11L, null, "", "Build and scale your online business effectively", new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Marina Bay Financial Centre, Singapore", "E-commerce Mastery", "guest-user-22222", new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 12L, null, "", "An evening of modern dance and artistic expression", new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Victoria Theatre, Singapore", "Contemporary Dance Performance", "empty-user-12345", new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 13L, null, "", "Essential cybersecurity practices for businesses", new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Singapore Management University", "Cybersecurity Awareness Training", "guest-user-22222", new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 14L, null, "", "Develop essential leadership skills for modern managers", new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Orchard Hotel Singapore", "Leadership Excellence Workshop", "empty-user-12345", new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }, - { 15L, null, "", "Independent filmmakers present their latest works", new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "Singapore International Film Festival Venue", "Film & Media Production Showcase", "guest-user-22222", new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) } - }); - - migrationBuilder.InsertData( - table: "Bookings", - columns: new[] { "BookingId", "AttendeeId", "CancellationDateTime", "CheckInDateTime", "CheckoutDateTime", "CreationDateTime", "GatheringId" }, - values: new object[,] - { - { "book_abc123456", "guest-user-22222", null, null, null, new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 1L }, - { "book_def789012", "empty-user-12345", null, null, null, new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 2L } - }); - - migrationBuilder.InsertData( - table: "GatheringCategoryDetails", - columns: new[] { "CategoryId", "GatheringId" }, - values: new object[,] - { - { 1L, 1L }, - { 2L, 2L }, - { 3L, 3L }, - { 1L, 4L }, - { 2L, 5L }, - { 3L, 6L }, - { 1L, 7L }, - { 2L, 8L }, - { 3L, 9L }, - { 1L, 10L }, - { 2L, 11L }, - { 3L, 12L }, - { 1L, 13L }, - { 2L, 14L }, - { 3L, 15L } - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_Bookings_AttendeeId", - table: "Bookings", - column: "AttendeeId"); - - migrationBuilder.CreateIndex( - name: "IX_Bookings_GatheringId", - table: "Bookings", - column: "GatheringId"); - - migrationBuilder.CreateIndex( - name: "IX_GatheringCategoryDetails_CategoryId", - table: "GatheringCategoryDetails", - column: "CategoryId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "Bookings"); - - migrationBuilder.DropTable( - name: "GatheringCategoryDetails"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - - migrationBuilder.DropTable( - name: "Categories"); - - migrationBuilder.DropTable( - name: "Gatherings"); - } - } -} diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.cs b/src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.cs deleted file mode 100644 index ddaadcc..0000000 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Evently.Server.Common.Adapters.Data.Migrations -{ - /// - public partial class UpdateSeededDates : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_abc123456", - column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_def789012", - column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 1L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 2L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 3L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 4L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 5L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 6L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 7L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 8L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 9L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 10L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 11L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 12L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 13L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 14L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 15L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_abc123456", - column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_def789012", - column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 1L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 2L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 3L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 4L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 5L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 6L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 7L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 8L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 9L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 10L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 11L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 12L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 13L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 14L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 15L, - columns: new[] { "End", "Start" }, - values: new object[] { new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) }); - } - } -} diff --git a/src/Evently.Server/Common/Adapters/Blazor/BlazorApp.razor b/src/Evently.Server/Common/Blazor/BlazorApp.razor similarity index 100% rename from src/Evently.Server/Common/Adapters/Blazor/BlazorApp.razor rename to src/Evently.Server/Common/Blazor/BlazorApp.razor diff --git a/src/Evently.Server/Common/Adapters/Blazor/Routes.razor b/src/Evently.Server/Common/Blazor/Routes.razor similarity index 100% rename from src/Evently.Server/Common/Adapters/Blazor/Routes.razor rename to src/Evently.Server/Common/Blazor/Routes.razor diff --git a/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs b/src/Evently.Server/Common/Data/AppDbContext.cs similarity index 87% rename from src/Evently.Server/Common/Adapters/Data/AppDbContext.cs rename to src/Evently.Server/Common/Data/AppDbContext.cs index cbf9340..8fae999 100644 --- a/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs +++ b/src/Evently.Server/Common/Data/AppDbContext.cs @@ -1,11 +1,11 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System.Reflection; -namespace Evently.Server.Common.Adapters.Data; +namespace Evently.Server.Common.Data; public class AppDbContext(DbContextOptions options) : IdentityDbContext(options) { public DbSet Accounts { get; set; } @@ -92,8 +92,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Tech Innovation Summit", Description = "A comprehensive summit exploring the latest in AI and machine learning", Location = "Marina Bay Sands Convention Centre, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 9, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 17, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 9, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 17, minute: 0, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -101,8 +103,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Startup Networking Night", Description = "Connect with fellow entrepreneurs and investors", Location = "Clarke Quay Central, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 18, minute: 30, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 22, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 18, minute: 30, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 22, minute: 0, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -110,8 +114,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Digital Art Exhibition", Description = "Showcasing contemporary digital art from emerging artists", Location = "National Gallery Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 10, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 18, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 10, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 18, minute: 0, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -119,8 +125,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Web Development Workshop", Description = "Learn modern web development techniques and best practices", Location = "Singapore Science Centre", - Start = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 13, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 17, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 13, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 17, minute: 0, second: 0, + singaporeOffset), OrganiserId = guestUserId, }, new Gathering { @@ -128,8 +136,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Business Strategy Seminar", Description = "Advanced strategies for scaling your business", Location = "Raffles City Convention Centre, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 14, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 16, minute: 30, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 14, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 16, minute: 30, second: 0, + singaporeOffset), OrganiserId = guestUserId, }, new Gathering { @@ -137,8 +147,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Photography Masterclass", Description = "Professional photography techniques and portfolio building", Location = "Gardens by the Bay, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 8, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 12, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 8, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 12, minute: 0, second: 0, + singaporeOffset), OrganiserId = guestUserId, }, new Gathering { @@ -146,8 +158,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Mobile App Development Bootcamp", Description = "Intensive bootcamp covering iOS and Android development", Location = "NUS School of Computing, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 9, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 18, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 9, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 18, minute: 0, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -155,8 +169,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Investment & Finance Forum", Description = "Learn about personal finance and investment strategies", Location = "Suntec Singapore Convention Centre", - Start = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 14, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 17, minute: 30, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 14, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 17, minute: 30, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -164,8 +180,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Creative Writing Workshop", Description = "Explore storytelling techniques and creative expression", Location = "Esplanade Theatres, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 10, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 15, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 10, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 15, minute: 0, second: 0, + singaporeOffset), OrganiserId = guestUserId, }, new Gathering { @@ -173,8 +191,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Cloud Computing Conference", Description = "Latest trends in cloud architecture and DevOps", Location = "Singapore EXPO", - Start = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 9, minute: 30, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 17, minute: 30, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 9, minute: 30, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 17, minute: 30, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -182,7 +202,8 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "E-commerce Mastery", Description = "Build and scale your online business effectively", Location = "Marina Bay Financial Centre, Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 3, hour: 13, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2027, month: 1, day: 3, hour: 13, minute: 0, second: 0, + singaporeOffset), End = new DateTimeOffset(year: 2027, month: 1, day: 3, hour: 18, minute: 0, second: 0, singaporeOffset), OrganiserId = guestUserId, }, @@ -191,7 +212,8 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Contemporary Dance Performance", Description = "An evening of modern dance and artistic expression", Location = "Victoria Theatre, Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 5, hour: 19, minute: 30, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2027, month: 1, day: 5, hour: 19, minute: 30, second: 0, + singaporeOffset), End = new DateTimeOffset(year: 2027, month: 1, day: 5, hour: 22, minute: 0, second: 0, singaporeOffset), OrganiserId = hostUserId, }, @@ -200,7 +222,8 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Cybersecurity Awareness Training", Description = "Essential cybersecurity practices for businesses", Location = "Singapore Management University", - Start = new DateTimeOffset(year: 2027, month: 1, day: 8, hour: 10, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2027, month: 1, day: 8, hour: 10, minute: 0, second: 0, + singaporeOffset), End = new DateTimeOffset(year: 2027, month: 1, day: 8, hour: 16, minute: 0, second: 0, singaporeOffset), OrganiserId = guestUserId, }, @@ -209,8 +232,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Leadership Excellence Workshop", Description = "Develop essential leadership skills for modern managers", Location = "Orchard Hotel Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 9, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 17, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 9, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 17, minute: 0, second: 0, + singaporeOffset), OrganiserId = hostUserId, }, new Gathering { @@ -218,8 +243,10 @@ private static void SeedData(ModelBuilder modelBuilder) { Name = "Film & Media Production Showcase", Description = "Independent filmmakers present their latest works", Location = "Singapore International Film Festival Venue", - Start = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 18, minute: 0, second: 0, singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 23, minute: 0, second: 0, singaporeOffset), + Start = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 18, minute: 0, second: 0, + singaporeOffset), + End = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 23, minute: 0, second: 0, + singaporeOffset), OrganiserId = guestUserId, } ); @@ -239,13 +266,15 @@ private static void SeedData(ModelBuilder modelBuilder) { new GatheringCategoryDetail { GatheringId = 11, CategoryId = 2 }, // E-commerce Mastery -> Business new GatheringCategoryDetail { GatheringId = 12, CategoryId = 3 }, // Contemporary Dance Performance -> Arts new GatheringCategoryDetail { GatheringId = 13, CategoryId = 1 }, // Cybersecurity Awareness Training -> IT - new GatheringCategoryDetail { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business + new GatheringCategoryDetail + { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business new GatheringCategoryDetail { GatheringId = 15, CategoryId = 3 } // Film & Media Production Showcase -> Arts ); // Seed Bookings with fixed DateTimeOffset values // Fixed DateTimeOffset for seeding (static value) - DateTimeOffset fixedCreationTime = new(year: 2025, month: 1, day: 1, hour: 0, minute: 0, second: 0, TimeSpan.Zero); + DateTimeOffset fixedCreationTime = + new(year: 2025, month: 1, day: 1, hour: 0, minute: 0, second: 0, TimeSpan.Zero); modelBuilder.Entity().HasData( new Booking { diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.Designer.cs b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.Designer.cs similarity index 99% rename from src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.Designer.cs rename to src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.Designer.cs index 8d23e5f..8f1f06a 100644 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/20250913035915_SQLServer.Designer.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Evently.Server.Common.Adapters.Data; +using Evently.Server.Common.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ #nullable disable -namespace Evently.Server.Common.Adapters.Data.Migrations +namespace Evently.Server.Common.Data.Migrations { [DbContext(typeof(AppDbContext))] [Migration("20250913035915_SQLServer")] diff --git a/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs new file mode 100644 index 0000000..1eab301 --- /dev/null +++ b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs @@ -0,0 +1,500 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Evently.Server.Common.Data.Migrations { + /// + public partial class SQLServer : Migration { + /// + protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new { + CategoryId = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + CategoryName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Approved = table.Column(type: "bit", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_Categories", x => x.CategoryId); + }); + + migrationBuilder.CreateTable( + name: "Gatherings", + columns: table => new { + GatheringId = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "nvarchar(max)", maxLength: 10000, nullable: false), + Start = table.Column(type: "datetimeoffset", nullable: false), + End = table.Column(type: "datetimeoffset", nullable: false), + Location = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + CoverSrc = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + OrganiserId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_Gatherings", x => x.GatheringId); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Bookings", + columns: table => new { + BookingId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + AttendeeId = table.Column(type: "nvarchar(450)", nullable: false), + GatheringId = table.Column(type: "bigint", nullable: false), + CreationDateTime = table.Column(type: "datetimeoffset", nullable: false), + CheckInDateTime = table.Column(type: "datetimeoffset", nullable: true), + CheckoutDateTime = table.Column(type: "datetimeoffset", nullable: true), + CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) + }, + constraints: table => { + table.PrimaryKey("PK_Bookings", x => x.BookingId); + table.ForeignKey( + name: "FK_Bookings_AspNetUsers_AttendeeId", + column: x => x.AttendeeId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Bookings_Gatherings_GatheringId", + column: x => x.GatheringId, + principalTable: "Gatherings", + principalColumn: "GatheringId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GatheringCategoryDetails", + columns: table => new { + GatheringId = table.Column(type: "bigint", nullable: false), + CategoryId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_GatheringCategoryDetails", x => new { x.GatheringId, x.CategoryId }); + table.ForeignKey( + name: "FK_GatheringCategoryDetails_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GatheringCategoryDetails_Gatherings_GatheringId", + column: x => x.GatheringId, + principalTable: "Gatherings", + principalColumn: "GatheringId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "AspNetUsers", + columns: new[] { + "Id", "AccessFailedCount", "ConcurrencyStamp", "Email", "EmailConfirmed", "LockoutEnabled", + "LockoutEnd", "Name", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", + "PhoneNumberConfirmed", "SecurityStamp", "TwoFactorEnabled", "UserName" + }, + values: new object[,] { + { + "empty-user-12345", 0, "EMPTY-CONCURRENCY-STAMP-12345", "empty@example.com", false, true, null, + "Empty User", "EMPTY@EXAMPLE.COM", "EMPTY_USER", null, null, false, + "EMPTY-SECURITY-STAMP-12345", false, "empty_user" + }, { + "guest-user-22222", 0, "EMPTY-CONCURRENCY-STAMP-12345", "guest@example.com", false, true, null, + "Guest User", "GUEST@EXAMPLE.COM", "GUEST_USER_2", null, null, false, + "EMPTY-SECURITY-STAMP-12345", false, "guest_user2" + } + }); + + migrationBuilder.InsertData( + table: "Categories", + columns: new[] { "CategoryId", "Approved", "CategoryName" }, + values: new object[,] { + { 1L, false, "Information Technology" }, + { 2L, false, "Business & Networking" }, + { 3L, false, "Arts & Culture" } + }); + + migrationBuilder.InsertData( + table: "Gatherings", + columns: new[] { + "GatheringId", "CancellationDateTime", "CoverSrc", "Description", "End", "Location", "Name", + "OrganiserId", "Start" + }, + values: new object[,] { + { + 1L, null, "", "A comprehensive summit exploring the latest in AI and machine learning", + new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Marina Bay Sands Convention Centre, Singapore", "Tech Innovation Summit", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 2L, null, "", "Connect with fellow entrepreneurs and investors", + new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Clarke Quay Central, Singapore", "Startup Networking Night", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 3L, null, "", "Showcasing contemporary digital art from emerging artists", + new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "National Gallery Singapore", "Digital Art Exhibition", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 4L, null, "", "Learn modern web development techniques and best practices", + new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Singapore Science Centre", "Web Development Workshop", "guest-user-22222", + new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 5L, null, "", "Advanced strategies for scaling your business", + new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Raffles City Convention Centre, Singapore", "Business Strategy Seminar", "guest-user-22222", + new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 6L, null, "", "Professional photography techniques and portfolio building", + new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Gardens by the Bay, Singapore", "Photography Masterclass", "guest-user-22222", + new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 7L, null, "", "Intensive bootcamp covering iOS and Android development", + new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "NUS School of Computing, Singapore", "Mobile App Development Bootcamp", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 8L, null, "", "Learn about personal finance and investment strategies", + new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Suntec Singapore Convention Centre", "Investment & Finance Forum", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 9L, null, "", "Explore storytelling techniques and creative expression", + new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Esplanade Theatres, Singapore", "Creative Writing Workshop", "guest-user-22222", + new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 10L, null, "", "Latest trends in cloud architecture and DevOps", + new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Singapore EXPO", "Cloud Computing Conference", "empty-user-12345", + new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 11L, null, "", "Build and scale your online business effectively", + new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Marina Bay Financial Centre, Singapore", "E-commerce Mastery", "guest-user-22222", + new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 12L, null, "", "An evening of modern dance and artistic expression", + new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Victoria Theatre, Singapore", "Contemporary Dance Performance", "empty-user-12345", + new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 13L, null, "", "Essential cybersecurity practices for businesses", + new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Singapore Management University", "Cybersecurity Awareness Training", "guest-user-22222", + new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 14L, null, "", "Develop essential leadership skills for modern managers", + new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Orchard Hotel Singapore", "Leadership Excellence Workshop", "empty-user-12345", + new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }, { + 15L, null, "", "Independent filmmakers present their latest works", + new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + "Singapore International Film Festival Venue", "Film & Media Production Showcase", + "guest-user-22222", + new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + } + }); + + migrationBuilder.InsertData( + table: "Bookings", + columns: new[] { + "BookingId", "AttendeeId", "CancellationDateTime", "CheckInDateTime", "CheckoutDateTime", + "CreationDateTime", "GatheringId" + }, + values: new object[,] { + { + "book_abc123456", "guest-user-22222", null, null, null, + new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + 1L + }, { + "book_def789012", "empty-user-12345", null, null, null, + new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + 2L + } + }); + + migrationBuilder.InsertData( + table: "GatheringCategoryDetails", + columns: new[] { "CategoryId", "GatheringId" }, + values: new object[,] { + { 1L, 1L }, + { 2L, 2L }, + { 3L, 3L }, + { 1L, 4L }, + { 2L, 5L }, + { 3L, 6L }, + { 1L, 7L }, + { 2L, 8L }, + { 3L, 9L }, + { 1L, 10L }, + { 2L, 11L }, + { 3L, 12L }, + { 1L, 13L }, + { 2L, 14L }, + { 3L, 15L } + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Bookings_AttendeeId", + table: "Bookings", + column: "AttendeeId"); + + migrationBuilder.CreateIndex( + name: "IX_Bookings_GatheringId", + table: "Bookings", + column: "GatheringId"); + + migrationBuilder.CreateIndex( + name: "IX_GatheringCategoryDetails_CategoryId", + table: "GatheringCategoryDetails", + column: "CategoryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "Bookings"); + + migrationBuilder.DropTable( + name: "GatheringCategoryDetails"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Gatherings"); + } + } +} \ No newline at end of file diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs similarity index 99% rename from src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs rename to src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs index 8722a0d..781f997 100644 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs @@ -1,6 +1,7 @@ // using System; using Evently.Server.Common.Adapters.Data; +using Evently.Server.Common.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +10,7 @@ #nullable disable -namespace Evently.Server.Common.Adapters.Data.Migrations +namespace Evently.Server.Common.Data.Migrations { [DbContext(typeof(AppDbContext))] [Migration("20250927053802_UpdateSeededDates")] diff --git a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs new file mode 100644 index 0000000..3cc32c9 --- /dev/null +++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs @@ -0,0 +1,407 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Evently.Server.Common.Data.Migrations { + /// + public partial class UpdateSeededDates : Migration { + /// + protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_abc123456", + column: "CreationDateTime", + value: new DateTimeOffset(new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_def789012", + column: "CreationDateTime", + value: new DateTimeOffset(new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 1L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 2L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 3L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 4L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 5L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 6L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 7L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 8L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 9L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 10L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 11L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 12L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 13L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 14L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 15L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_abc123456", + column: "CreationDateTime", + value: new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_def789012", + column: "CreationDateTime", + value: new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 1L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 2L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 3L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 4L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 5L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 6L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 7L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 8L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 9L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 10L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 11L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 12L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 13L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 14L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 15L, + columns: new[] { "End", "Start" }, + values: new object[] { + new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)), + new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0)) + }); + } + } +} \ No newline at end of file diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs b/src/Evently.Server/Common/Data/Migrations/AppDbContextModelSnapshot.cs similarity index 99% rename from src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs rename to src/Evently.Server/Common/Data/Migrations/AppDbContextModelSnapshot.cs index 97bed42..aa67b68 100644 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Evently.Server/Common/Data/Migrations/AppDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Evently.Server.Common.Adapters.Data; +using Evently.Server.Common.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -8,7 +8,7 @@ #nullable disable -namespace Evently.Server.Common.Adapters.Data.Migrations +namespace Evently.Server.Common.Data.Migrations { [DbContext(typeof(AppDbContext))] partial class AppDbContextModelSnapshot : ModelSnapshot diff --git a/src/Evently.Server/Common/Extensions/MapperExtension.cs b/src/Evently.Server/Common/Extensions/MapperExtension.cs index b8bf244..a4dc87b 100644 --- a/src/Evently.Server/Common/Extensions/MapperExtension.cs +++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs @@ -1,5 +1,5 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Models; namespace Evently.Server.Common.Extensions; diff --git a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs index 51a1105..64440a8 100644 --- a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs +++ b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Models; using Microsoft.Extensions.Options; namespace Evently.Server.Common.Extensions; diff --git a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs index 899e8dc..327df06 100644 --- a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs +++ b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs @@ -3,8 +3,8 @@ namespace Evently.Server.Common.Middlewares; public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler { - - public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { + public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, + CancellationToken cancellationToken) { string exceptionMessage = exception.Message; logger.LogError( "Error Message: {exceptionMessage}, Time of occurrence {time}", diff --git a/src/Evently.Server/Common/Domains/Entities/Account.cs b/src/Evently.Server/Domains/Entities/Account.cs similarity index 90% rename from src/Evently.Server/Common/Domains/Entities/Account.cs rename to src/Evently.Server/Domains/Entities/Account.cs index c635b1d..3c13971 100644 --- a/src/Evently.Server/Common/Domains/Entities/Account.cs +++ b/src/Evently.Server/Domains/Entities/Account.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Entities; +namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] diff --git a/src/Evently.Server/Common/Domains/Entities/Booking.cs b/src/Evently.Server/Domains/Entities/Booking.cs similarity index 86% rename from src/Evently.Server/Common/Domains/Entities/Booking.cs rename to src/Evently.Server/Domains/Entities/Booking.cs index 600ae0e..c8cce03 100644 --- a/src/Evently.Server/Common/Domains/Entities/Booking.cs +++ b/src/Evently.Server/Domains/Entities/Booking.cs @@ -1,12 +1,12 @@ -using Evently.Server.Common.Domains.Models; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Models; using NanoidDotNet; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace Evently.Server.Common.Domains.Entities; +namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] @@ -17,8 +17,10 @@ public class Booking { public string BookingId { get; set; } = $"book_{Nanoid.Generate(size: 10)}"; [ForeignKey("Account")] - [SuppressMessage("ReSharper", "EntityFramework.ModelValidation.UnlimitedStringLength", Justification = "MSSQL does not allow specified string limit")] + [SuppressMessage("ReSharper", "EntityFramework.ModelValidation.UnlimitedStringLength", + Justification = "MSSQL does not allow specified string limit")] public string AttendeeId { get; set; } = string.Empty; + [JsonIgnore] public Account? Account { get; set; } [NotMapped] public AccountDto? AccountDto => Account?.ToAccountDto(); diff --git a/src/Evently.Server/Common/Domains/Entities/Category.cs b/src/Evently.Server/Domains/Entities/Category.cs similarity index 90% rename from src/Evently.Server/Common/Domains/Entities/Category.cs rename to src/Evently.Server/Domains/Entities/Category.cs index 5d26c69..5ad6a1b 100644 --- a/src/Evently.Server/Common/Domains/Entities/Category.cs +++ b/src/Evently.Server/Domains/Entities/Category.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Entities; +namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] diff --git a/src/Evently.Server/Common/Domains/Entities/Gathering.cs b/src/Evently.Server/Domains/Entities/Gathering.cs similarity index 90% rename from src/Evently.Server/Common/Domains/Entities/Gathering.cs rename to src/Evently.Server/Domains/Entities/Gathering.cs index 9d990fa..74a410b 100644 --- a/src/Evently.Server/Common/Domains/Entities/Gathering.cs +++ b/src/Evently.Server/Domains/Entities/Gathering.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Entities; +namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] @@ -24,7 +24,9 @@ public class Gathering { // convenience field that acts as a readonly field for Account that created the Gathering [ForeignKey("Account")] - [StringLength(100)] public string OrganiserId { get; set; } = string.Empty; + [StringLength(100)] + public string OrganiserId { get; set; } = string.Empty; + public DateTimeOffset? CancellationDateTime { get; set; } public List Bookings { get; set; } = []; diff --git a/src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs similarity index 90% rename from src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs rename to src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs index a01019c..b6b8f1d 100644 --- a/src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs +++ b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Entities; +namespace Evently.Server.Domains.Entities; [PrimaryKey(propertyName: nameof(GatheringId), nameof(CategoryId))] [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] diff --git a/src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs similarity index 74% rename from src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs rename to src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs index 556d6c3..227e6d1 100644 --- a/src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs +++ b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs @@ -1,4 +1,4 @@ -namespace Evently.Server.Common.Domains.Exceptions; +namespace Evently.Server.Domains.Exceptions; public class ExternalLoginProviderException(string provider, string message) : Exception($"External login provider: {provider} error occurred: {message}"); \ No newline at end of file diff --git a/src/Evently.Server/Common/Domains/Interfaces/IAccountsService.cs b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs similarity index 69% rename from src/Evently.Server/Common/Domains/Interfaces/IAccountsService.cs rename to src/Evently.Server/Domains/Interfaces/IAccountsService.cs index 182095e..e5e7637 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IAccountsService.cs +++ b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using System.Security.Claims; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface IAccountsService { Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider); diff --git a/src/Evently.Server/Common/Domains/Interfaces/IBookingService.cs b/src/Evently.Server/Domains/Interfaces/IBookingService.cs similarity index 69% rename from src/Evently.Server/Common/Domains/Interfaces/IBookingService.cs rename to src/Evently.Server/Domains/Interfaces/IBookingService.cs index 70c0695..7a35638 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IBookingService.cs +++ b/src/Evently.Server/Domains/Interfaces/IBookingService.cs @@ -1,14 +1,17 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Models; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface IBookingService { Task GetBooking(string bookingId); + Task> GetBookings(string? accountId, long? gatheringId, DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, + DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, bool? isCancelled, int? offset, int? limit); + Task CreateBooking(BookingReqDto bookingReqDto); Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto); Task RenderTicket(string bookingId); diff --git a/src/Evently.Server/Common/Domains/Interfaces/ICategoryService.cs b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs similarity index 53% rename from src/Evently.Server/Common/Domains/Interfaces/ICategoryService.cs rename to src/Evently.Server/Domains/Interfaces/ICategoryService.cs index 6e6f7b5..d08f46e 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/ICategoryService.cs +++ b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Models; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface ICategoryService { Task> GetCategories(long? gatheringId, bool? approved); diff --git a/src/Evently.Server/Common/Domains/Interfaces/IEmailerAdapter.cs b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs similarity index 82% rename from src/Evently.Server/Common/Domains/Interfaces/IEmailerAdapter.cs rename to src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs index e5affa4..a996a49 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IEmailerAdapter.cs +++ b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs @@ -1,4 +1,4 @@ -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface IEmailerAdapter { // senderEmail: Either actual Sender email or email of the third party that IEmailer sends on behalf of. diff --git a/src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs similarity index 80% rename from src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs rename to src/Evently.Server/Domains/Interfaces/IGatheringService.cs index e1ad4f8..2a303ee 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs +++ b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Models; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface IGatheringService { Task GetGathering(long gatheringId); diff --git a/src/Evently.Server/Common/Domains/Interfaces/IMediaRenderer.cs b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs similarity index 88% rename from src/Evently.Server/Common/Domains/Interfaces/IMediaRenderer.cs rename to src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs index f2ee977..072c7fe 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IMediaRenderer.cs +++ b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Components; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; public interface IMediaRenderer { Task RenderComponentHtml(Dictionary dictionary) where T : IComponent; diff --git a/src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs similarity index 84% rename from src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs rename to src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs index 2763b08..65d810b 100644 --- a/src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs +++ b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs @@ -1,12 +1,14 @@ using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Interfaces; +namespace Evently.Server.Domains.Interfaces; [SuppressMessage("ReSharper", "UnusedMember.Global")] public interface IObjectStorageService { // mimeType determines the browser's behaviour when the file is accessed directly via the browser, // e.g. download ("application/octet-stream") or view ("images/*)" the file. - Task UploadFile(string containerName, string fileName, BinaryData binaryData, string mimeType = "application/octet-stream"); + Task UploadFile(string containerName, string fileName, BinaryData binaryData, + string mimeType = "application/octet-stream"); + Task GetFileUri(string containerName, string fileName); Task GetFile(string containerName, string fileName); Task IsFileExists(string containerName, string fileName); diff --git a/src/Evently.Server/Common/Domains/Models/AccountDto.cs b/src/Evently.Server/Domains/Models/AccountDto.cs similarity index 74% rename from src/Evently.Server/Common/Domains/Models/AccountDto.cs rename to src/Evently.Server/Domains/Models/AccountDto.cs index 6f38ea7..96caeba 100644 --- a/src/Evently.Server/Common/Domains/Models/AccountDto.cs +++ b/src/Evently.Server/Domains/Models/AccountDto.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; [UsedImplicitly] public sealed record AccountDto(string Id, string Email, string Username, string Name); \ No newline at end of file diff --git a/src/Evently.Server/Common/Domains/Models/BookingReqDto.cs b/src/Evently.Server/Domains/Models/BookingReqDto.cs similarity index 82% rename from src/Evently.Server/Common/Domains/Models/BookingReqDto.cs rename to src/Evently.Server/Domains/Models/BookingReqDto.cs index e0028c0..6b631d2 100644 --- a/src/Evently.Server/Common/Domains/Models/BookingReqDto.cs +++ b/src/Evently.Server/Domains/Models/BookingReqDto.cs @@ -1,4 +1,4 @@ -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; public sealed record BookingReqDto( string BookingId, diff --git a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs b/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs similarity index 73% rename from src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs rename to src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs index 24127ea..4604018 100644 --- a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs +++ b/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; [UsedImplicitly] public sealed record GatheringCategoryDetailDto(long GatheringId, long CategoryId); \ No newline at end of file diff --git a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs b/src/Evently.Server/Domains/Models/GatheringReqDto.cs similarity index 86% rename from src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs rename to src/Evently.Server/Domains/Models/GatheringReqDto.cs index 1f45531..c6e8121 100644 --- a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs +++ b/src/Evently.Server/Domains/Models/GatheringReqDto.cs @@ -1,4 +1,4 @@ -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; public sealed record GatheringReqDto( long GatheringId, diff --git a/src/Evently.Server/Common/Domains/Models/PageResult.cs b/src/Evently.Server/Domains/Models/PageResult.cs similarity index 71% rename from src/Evently.Server/Common/Domains/Models/PageResult.cs rename to src/Evently.Server/Domains/Models/PageResult.cs index 217de02..c15fcca 100644 --- a/src/Evently.Server/Common/Domains/Models/PageResult.cs +++ b/src/Evently.Server/Domains/Models/PageResult.cs @@ -1,4 +1,4 @@ -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; public sealed class PageResult { public List Items { get; init; } = []; diff --git a/src/Evently.Server/Common/Domains/Models/Settings.cs b/src/Evently.Server/Domains/Models/Settings.cs similarity index 96% rename from src/Evently.Server/Common/Domains/Models/Settings.cs rename to src/Evently.Server/Domains/Models/Settings.cs index 073e9e7..30fa938 100644 --- a/src/Evently.Server/Common/Domains/Models/Settings.cs +++ b/src/Evently.Server/Domains/Models/Settings.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; -namespace Evently.Server.Common.Domains.Models; +namespace Evently.Server.Domains.Models; public sealed class Settings { public StorageAccount StorageAccount { get; init; } = new(); diff --git a/src/Evently.Server/Evently.Server.csproj b/src/Evently.Server/Evently.Server.csproj index 839adac..c1bfee0 100644 --- a/src/Evently.Server/Evently.Server.csproj +++ b/src/Evently.Server/Evently.Server.csproj @@ -50,6 +50,12 @@ - + <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\BlazorApp.razor" /> + <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\Routes.razor" /> + + + + + \ No newline at end of file diff --git a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs index 6f240fc..a90fbf8 100644 --- a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs +++ b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Exceptions; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Extensions; +using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Exceptions; +using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; diff --git a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs index 333de27..82ef210 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using System.Security.Claims; diff --git a/src/Evently.Server/Features/Accounts/Services/AccountService.cs b/src/Evently.Server/Features/Accounts/Services/AccountService.cs index dead37a..f679b85 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountService.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountService.cs @@ -1,6 +1,6 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Exceptions; -using Evently.Server.Common.Domains.Interfaces; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Exceptions; +using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Identity; using System.Security.Claims; using System.Text.RegularExpressions; diff --git a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs index f5aa168..48b6ed4 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using FluentValidation; namespace Evently.Server.Features.Accounts.Services; diff --git a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs index a09db98..aac0060 100644 --- a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs +++ b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Evently.Server.Features.Accounts.Services; using Microsoft.AspNetCore.Mvc; using System.Globalization; @@ -11,7 +11,10 @@ namespace Evently.Server.Features.Bookings.Controllers; [ApiController] [Route("api/v1/[controller]")] -public sealed class BookingsController(IBookingService bookingService, ChannelWriter emailQueue, ILogger logger) +public sealed class BookingsController( + IBookingService bookingService, + ChannelWriter emailQueue, + ILogger logger) : ControllerBase { [HttpGet("{bookingId}", Name = "GetBooking")] public async Task> GetBooking(string bookingId) { @@ -35,7 +38,8 @@ public async Task> GetBookings( long? gatheringId, DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, + DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, bool isCancelled, int? offset, int? limit) { diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index 5d2e709..de9be6b 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -1,15 +1,16 @@ using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Evently.Server.Features.Emails.Views; using FluentValidation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NanoidDotNet; using System.Text.Json; -using ValidationResult=FluentValidation.Results.ValidationResult; +using ValidationResult = FluentValidation.Results.ValidationResult; namespace Evently.Server.Features.Bookings.Services; @@ -33,7 +34,8 @@ public sealed class BookingService( public async Task> GetBookings(string? accountId, long? gatheringId, DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, + DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, bool? isCancelled, int? offset, int? limit) { IQueryable query = db.Bookings .Where((b) => accountId == null || b.AttendeeId == accountId) @@ -41,8 +43,10 @@ public async Task> GetBookings(string? accountId, long? gath .Where((c) => checkInStart == null || checkInStart <= c.CheckInDateTime) .Where((b) => checkInEnd == null || b.CheckInDateTime <= checkInEnd) .Where((b) => isCancelled == null || b.CancellationDateTime.HasValue == isCancelled) - .Where((b) => gatheringStartBefore == null || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore) - .Where((b) => gatheringStartAfter == null || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter) + .Where((b) => + gatheringStartBefore == null || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore) + .Where((b) => + gatheringStartAfter == null || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter) .Where((b) => gatheringEndBefore == null || b.Gathering != null && b.Gathering.End <= gatheringEndBefore) .Where((b) => gatheringEndAfter == null || b.Gathering != null && b.Gathering.End >= gatheringEndAfter) .Include((b) => b.Account) @@ -68,7 +72,8 @@ public async Task CreateBooking(BookingReqDto bookingReqDto) { Booking booking = bookingReqDto.ToBooking(); ValidationResult validationResult = await validator.ValidateAsync(booking); if (!validationResult.IsValid) { - throw new ArgumentException($"Account has already booked this gathering (GatheringId: {booking.GatheringId})"); + throw new ArgumentException( + $"Account has already booked this gathering (GatheringId: {booking.GatheringId})"); } booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}"; diff --git a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs index 5cf6c2f..223b6d1 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using FluentValidation; namespace Evently.Server.Features.Bookings.Services; diff --git a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs index 032ada0..f6bf919 100644 --- a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs +++ b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs @@ -1,6 +1,6 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Microsoft.AspNetCore.Mvc; using System.Globalization; diff --git a/src/Evently.Server/Features/Categories/Services/CategoryService.cs b/src/Evently.Server/Features/Categories/Services/CategoryService.cs index 11a1d86..fd78105 100644 --- a/src/Evently.Server/Features/Categories/Services/CategoryService.cs +++ b/src/Evently.Server/Features/Categories/Services/CategoryService.cs @@ -1,7 +1,8 @@ using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Data; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Microsoft.EntityFrameworkCore; namespace Evently.Server.Features.Categories.Services; @@ -17,7 +18,8 @@ public async Task> GetCategories(long? gatheringId, bool? a IQueryable query = db.Categories .Include((category) => category.GatheringCategoryDetails) .Where((category) => - gatheringId == null || category.GatheringCategoryDetails.Any((detail) => detail.GatheringId == gatheringId)) + gatheringId == null || + category.GatheringCategoryDetails.Any((detail) => detail.GatheringId == gatheringId)) .Where((category) => approved == null || category.Approved == approved); int totalCount = await query.CountAsync(); diff --git a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs index 2c52b9d..3cfa795 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs @@ -1,6 +1,6 @@ -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using MailKit.Net.Smtp; using Microsoft.Extensions.Options; using MimeKit; diff --git a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs index d6e9a7a..cec198b 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs @@ -1,6 +1,6 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Extensions; +using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; using System.Threading.Channels; namespace Evently.Server.Features.Emails.Services; @@ -22,6 +22,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (booking?.Account?.Email is null) { continue; } + Account account = booking.Account; string html = await bookingService.RenderTicket(bookingId); diff --git a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs index a232daa..c91b59f 100644 --- a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs +++ b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs @@ -1,11 +1,11 @@ -using Evently.Server.Common.Domains.Interfaces; +using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web.HtmlRendering; using PdfSharp; using PdfSharp.Pdf; using QRCoder; using TheArtOfDev.HtmlRenderer.PdfSharp; -using BlazorHtmlRenderer=Microsoft.AspNetCore.Components.Web.HtmlRenderer; +using BlazorHtmlRenderer = Microsoft.AspNetCore.Components.Web.HtmlRenderer; namespace Evently.Server.Features.Emails.Services; diff --git a/src/Evently.Server/Features/Emails/Views/Ticket.razor b/src/Evently.Server/Features/Emails/Views/Ticket.razor index 14f886f..9f36cc7 100644 --- a/src/Evently.Server/Features/Emails/Views/Ticket.razor +++ b/src/Evently.Server/Features/Emails/Views/Ticket.razor @@ -1,4 +1,4 @@ -@using Evently.Server.Common.Domains.Entities +@using Evently.Server.Domains.Entities @using Evently.Server.Features.Emails.Views.Components @inject NavigationManager Navigator @inject IWebHostEnvironment Env @@ -31,8 +31,10 @@
-

Your booking has been - confirmed!

+

+ Your booking has been + confirmed! +

@@ -68,7 +70,8 @@
-
DATE & +
+ DATE & TIME
@StartDate - @EndDate
@@ -81,7 +84,8 @@
-
LOCATION +
+ LOCATION
@Gathering.Location
@@ -93,7 +97,8 @@
-
ATTENDEE +
+ ATTENDEE
@@ -120,7 +125,9 @@
BOOKING ID: @Booking.BookingId + style="color: #495057; font-size: 12px; font-weight: 600; font-family: 'Courier New', monospace;"> + @Booking.BookingId +
@@ -142,8 +149,10 @@ [Parameter] [EditorRequired] public string QrCodeUrl { get; set; } = string.Empty; [Parameter] public string Classes { get; set; } = string.Empty; - protected override void OnAfterRender(bool firstRender) { - if (firstRender) { + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { _baseUri = (Env.IsDevelopment() ? "https://localhost:5000/" : Navigator.BaseUri).TrimEnd('/'); } } diff --git a/src/Evently.Server/Features/Files/Controllers/FilesController.cs b/src/Evently.Server/Features/Files/Controllers/FilesController.cs index a22e577..a42ca30 100644 --- a/src/Evently.Server/Features/Files/Controllers/FilesController.cs +++ b/src/Evently.Server/Features/Files/Controllers/FilesController.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Interfaces; +using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using System.ComponentModel.DataAnnotations; @@ -7,7 +7,8 @@ namespace Evently.Server.Features.Files.Controllers; [ApiController] [Route("api/v1/[controller]")] -public class FilesController(ILogger logger, IObjectStorageService objectStorageService) : ControllerBase { +public class FilesController(ILogger logger, IObjectStorageService objectStorageService) + : ControllerBase { [HttpGet("object-storage/{bucket}", Name = "GetFile")] public async Task GetFile(string bucket, [Required] [FromQuery] string fileName) { logger.LogInformation("fileName: {}", fileName); @@ -29,6 +30,7 @@ private static string GetContentType(string fileName) { if (!provider.TryGetContentType(fileName, contentType: out string? contentType)) { contentType = "application/octet-stream"; // Default fallback } + return contentType; } } \ No newline at end of file diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index add97e3..17e3892 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -2,9 +2,9 @@ using Azure.AI.ContentSafety; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Microsoft.Extensions.Options; namespace Evently.Server.Features.Files.Services; @@ -26,7 +26,8 @@ public ObjectStorageService(IOptions settings, ILogger> GetGathering(long gatheringId) { if (customer is null) { return NotFound(); } + return Ok(customer); } @@ -34,7 +35,8 @@ public async Task> GetGathering(long gatheringId) { public async Task>> GetGatherings(string? attendeeId, string? organiserId, string? name, - DateTimeOffset? startDateBefore, DateTimeOffset? startDateAfter, DateTimeOffset? endDateBefore, DateTimeOffset? endDateAfter, + DateTimeOffset? startDateBefore, DateTimeOffset? startDateAfter, DateTimeOffset? endDateBefore, + DateTimeOffset? endDateAfter, bool? isCancelled, [FromQuery(Name = "categoryIds[]")] long[]? categoryIds, int? offset, int? limit) { @@ -58,7 +60,8 @@ public async Task>> GetGatherings(string? attendeeI } [HttpPost("", Name = "CreateGathering")] - public async Task> CreateGathering([FromForm] GatheringReqDto gatheringReqDto, [FromForm] IFormFile? coverImg) { + public async Task> CreateGathering([FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg) { gatheringReqDto = gatheringReqDto with { GatheringId = 0L }; AuthenticateResult authenticationResult = @@ -77,7 +80,8 @@ public async Task> CreateGathering([FromForm] GatheringR } [HttpPut("{gatheringId:long}", Name = "UpdateGathering")] - public async Task UpdateGathering(long gatheringId, [FromForm] GatheringReqDto gatheringReqDto, [FromForm] IFormFile? coverImg) { + public async Task UpdateGathering(long gatheringId, [FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg) { Gathering? gathering = await gatheringService.GetGathering(gatheringId); if (gathering is null) { return NotFound(); @@ -118,6 +122,7 @@ private async Task UploadCoverImage(long gatheringId, IFormFile coverImg if (!isContentSafe) { return string.Empty; } + Uri uri = await objectStorageService.UploadFile(_containerName, fileName, binaryData, diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index 755eb8e..bf80217 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -1,8 +1,9 @@ using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using FluentValidation; using FluentValidation.Results; using Microsoft.EntityFrameworkCore; @@ -39,7 +40,8 @@ public async Task> GetGatherings( .Where((gathering) => organiserId == null || gathering.OrganiserId == organiserId) .Where(gathering => isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled) .Where((gathering) => - categoryIds == null || categoryIds.Count == 0 || gathering.GatheringCategoryDetails.Any(detail => categoryIds.Contains(detail.CategoryId))) + categoryIds == null || categoryIds.Count == 0 || + gathering.GatheringCategoryDetails.Any(detail => categoryIds.Contains(detail.CategoryId))) .Where((gathering) => attendeeId == null || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId)) .Include(gathering => gathering.Bookings.Where((be) => be.AttendeeId == attendeeId)) diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs index f0ea341..4971980 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs @@ -1,4 +1,4 @@ -using Evently.Server.Common.Domains.Entities; +using Evently.Server.Domains.Entities; using FluentValidation; namespace Evently.Server.Features.Gatherings.Services; diff --git a/src/Evently.Server/Program.cs b/src/Evently.Server/Program.cs index 75b182f..eccdc69 100644 --- a/src/Evently.Server/Program.cs +++ b/src/Evently.Server/Program.cs @@ -1,10 +1,10 @@ -using Evently.Server.Common.Adapters.Blazor; -using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Blazor; +using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; using Evently.Server.Common.Middlewares; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Evently.Server.Features.Accounts.Services; using Evently.Server.Features.Bookings.Services; using Evently.Server.Features.Categories.Services; @@ -34,7 +34,6 @@ IOptions settings = builder.Services.LoadAppConfiguration(config); // register DB -// retrieve the heroku postgres db conn string, otherwise, get the local default string? dbConnStr = builder.Configuration.GetConnectionString("WebApiDatabase"); logger.LogValue("dbConnStr", dbConnStr); builder.Services.AddDbContext((options) => { diff --git a/src/evently.client/src/lib/domains/entities/entities.ts b/src/evently.client/src/domains/entities/entities.ts similarity index 100% rename from src/evently.client/src/lib/domains/entities/entities.ts rename to src/evently.client/src/domains/entities/entities.ts diff --git a/src/evently.client/src/lib/domains/entities/index.ts b/src/evently.client/src/domains/entities/index.ts similarity index 100% rename from src/evently.client/src/lib/domains/entities/index.ts rename to src/evently.client/src/domains/entities/index.ts diff --git a/src/evently.client/src/lib/domains/interfaces/index.ts b/src/evently.client/src/domains/interfaces/index.ts similarity index 100% rename from src/evently.client/src/lib/domains/interfaces/index.ts rename to src/evently.client/src/domains/interfaces/index.ts diff --git a/src/evently.client/src/lib/domains/interfaces/page-result.ts b/src/evently.client/src/domains/interfaces/page-result.ts similarity index 100% rename from src/evently.client/src/lib/domains/interfaces/page-result.ts rename to src/evently.client/src/domains/interfaces/page-result.ts diff --git a/src/evently.client/src/lib/domains/interfaces/route-context.ts b/src/evently.client/src/domains/interfaces/route-context.ts similarity index 100% rename from src/evently.client/src/lib/domains/interfaces/route-context.ts rename to src/evently.client/src/domains/interfaces/route-context.ts diff --git a/src/evently.client/src/lib/domains/models/index.ts b/src/evently.client/src/domains/models/index.ts similarity index 100% rename from src/evently.client/src/lib/domains/models/index.ts rename to src/evently.client/src/domains/models/index.ts diff --git a/src/evently.client/src/lib/domains/models/toast-content.ts b/src/evently.client/src/domains/models/toast-content.ts similarity index 100% rename from src/evently.client/src/lib/domains/models/toast-content.ts rename to src/evently.client/src/domains/models/toast-content.ts diff --git a/src/evently.client/src/lib/domains/models/upsert-dtos.ts b/src/evently.client/src/domains/models/upsert-dtos.ts similarity index 100% rename from src/evently.client/src/lib/domains/models/upsert-dtos.ts rename to src/evently.client/src/domains/models/upsert-dtos.ts diff --git a/src/evently.client/src/lib/components/-card.test.tsx b/src/evently.client/src/lib/components/-card.test.tsx index 73c6ac5..3f8f92d 100644 --- a/src/evently.client/src/lib/components/-card.test.tsx +++ b/src/evently.client/src/lib/components/-card.test.tsx @@ -1,6 +1,6 @@ import { render, screen, waitFor } from "@testing-library/react"; import { Card } from "~/lib/components/card.tsx"; -import { Gathering } from "~/lib/domains/entities"; +import { Gathering } from "~/domains/entities"; import { getMockGathering } from "~/lib/services/gathering-service.mock"; import { TestComponentWrapper, diff --git a/src/evently.client/src/lib/components/card.tsx b/src/evently.client/src/lib/components/card.tsx index 512083f..09db4af 100644 --- a/src/evently.client/src/lib/components/card.tsx +++ b/src/evently.client/src/lib/components/card.tsx @@ -2,7 +2,7 @@ import Placeholder2 from "~/lib/assets/event_placeholder_2.png"; import { type JSX } from "react"; import { Link } from "@tanstack/react-router"; -import { Category, Gathering } from "~/lib/domains/entities"; +import { Category, Gathering } from "~/domains/entities"; import { Icon } from "@iconify/react"; import { DateTime } from "luxon"; import { hashString } from "~/lib/services"; diff --git a/src/evently.client/src/lib/components/test-component-wrapper.tsx b/src/evently.client/src/lib/components/test-component-wrapper.tsx index a6eced9..627c042 100644 --- a/src/evently.client/src/lib/components/test-component-wrapper.tsx +++ b/src/evently.client/src/lib/components/test-component-wrapper.tsx @@ -1,5 +1,5 @@ import type { JSX, ReactNode } from "react"; -import { Account } from "~/lib/domains/entities"; +import { Account } from "~/domains/entities"; import { type AnyRoute, createMemoryHistory, @@ -9,7 +9,7 @@ import { Outlet, RouterProvider } from "@tanstack/react-router"; -import type { RouteContext } from "~/lib/domains/interfaces"; +import type { RouteContext } from "~/domains/interfaces"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; interface TestComponentProps { diff --git a/src/evently.client/src/lib/services/auth-service.ts b/src/evently.client/src/lib/services/auth-service.ts index 68fe31e..5a870d1 100644 --- a/src/evently.client/src/lib/services/auth-service.ts +++ b/src/evently.client/src/lib/services/auth-service.ts @@ -1,6 +1,6 @@ import { redirect } from "@tanstack/react-router"; import axios, { type AxiosResponse } from "axios"; -import { Account } from "~/lib/domains/entities"; +import { Account } from "~/domains/entities"; import { sleep } from "~/lib/services/util-service"; export async function login(redirectUrl: string) { diff --git a/src/evently.client/src/lib/services/booking-service.ts b/src/evently.client/src/lib/services/booking-service.ts index ff0cec2..afe9305 100644 --- a/src/evently.client/src/lib/services/booking-service.ts +++ b/src/evently.client/src/lib/services/booking-service.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { Booking } from "~/lib/domains/entities"; -import { BookingReqDto } from "~/lib/domains/models"; -import type { PageResult } from "~/lib/domains/interfaces"; +import { Booking } from "~/domains/entities"; +import { BookingReqDto } from "~/domains/models"; +import type { PageResult } from "~/domains/interfaces"; export interface GetBookingsParams { attendeeId?: string; diff --git a/src/evently.client/src/lib/services/category-service.ts b/src/evently.client/src/lib/services/category-service.ts index 1bc3152..a3fa3f2 100644 --- a/src/evently.client/src/lib/services/category-service.ts +++ b/src/evently.client/src/lib/services/category-service.ts @@ -1,4 +1,4 @@ -import { Category } from "~/lib/domains/entities"; +import { Category } from "~/domains/entities"; import axios from "axios"; export async function getCategories(): Promise { diff --git a/src/evently.client/src/lib/services/gathering-service.mock.ts b/src/evently.client/src/lib/services/gathering-service.mock.ts index 04615d3..8c92d6c 100644 --- a/src/evently.client/src/lib/services/gathering-service.mock.ts +++ b/src/evently.client/src/lib/services/gathering-service.mock.ts @@ -1,7 +1,7 @@ -import { Booking, Category, Gathering, GatheringCategoryDetail } from "~/lib/domains/entities"; -import { GatheringReqDto } from "~/lib/domains/models"; +import { Booking, Category, Gathering, GatheringCategoryDetail } from "~/domains/entities"; +import { GatheringReqDto } from "~/domains/models"; import type { GetGatheringsParams } from "./gathering-service"; -import type { PageResult } from "~/lib/domains/interfaces"; +import type { PageResult } from "~/domains/interfaces"; // Mock data for categories const mockCategories: Category[] = [ diff --git a/src/evently.client/src/lib/services/gathering-service.ts b/src/evently.client/src/lib/services/gathering-service.ts index f5efec2..b8e2518 100644 --- a/src/evently.client/src/lib/services/gathering-service.ts +++ b/src/evently.client/src/lib/services/gathering-service.ts @@ -1,7 +1,7 @@ -import type { Gathering } from "~/lib/domains/entities"; +import type { Gathering } from "~/domains/entities"; import axios from "axios"; -import { GatheringCategoryDetailReqDto, GatheringReqDto } from "~/lib/domains/models"; -import type { PageResult } from "~/lib/domains/interfaces"; +import { GatheringCategoryDetailReqDto, GatheringReqDto } from "~/domains/models"; +import type { PageResult } from "~/domains/interfaces"; import cloneDeep from "lodash.clonedeep"; export interface GetGatheringsParams { diff --git a/src/evently.client/src/routeTree.gen.ts b/src/evently.client/src/routeTree.gen.ts index 038453e..2b5ff9a 100644 --- a/src/evently.client/src/routeTree.gen.ts +++ b/src/evently.client/src/routeTree.gen.ts @@ -8,304 +8,301 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from './routes/__root' -import { Route as IndexRouteImport } from './routes/index' -import { Route as LoginIndexRouteImport } from './routes/login/index' -import { Route as HealthcheckIndexRouteImport } from './routes/healthcheck/index' -import { Route as GatheringsIndexRouteImport } from './routes/gatherings/index' -import { Route as BookingsauthRouteRouteImport } from './routes/bookings/(auth)/route' -import { Route as GatheringsGatheringIdIndexRouteImport } from './routes/gatherings/$gatheringId/index' -import { Route as GatheringsauthCreateRouteImport } from './routes/gatherings/(auth).create' -import { Route as BookingsauthHostingIndexRouteImport } from './routes/bookings/(auth)/hosting/index' -import { Route as BookingsauthAttendingIndexRouteImport } from './routes/bookings/(auth)/attending/index' -import { Route as GatheringsGatheringIdauthUpdateRouteImport } from './routes/gatherings/$gatheringId/(auth).update' -import { Route as BookingsauthHostingGatheringIdDashboardIndexRouteImport } from './routes/bookings/(auth)/hosting/$gatheringId/dashboard.index' -import { Route as BookingsauthHostingGatheringIdDashboardScanRouteImport } from './routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan' +import { Route as rootRouteImport } from "./routes/__root"; +import { Route as IndexRouteImport } from "./routes/index"; +import { Route as LoginIndexRouteImport } from "./routes/login/index"; +import { Route as HealthcheckIndexRouteImport } from "./routes/healthcheck/index"; +import { Route as GatheringsIndexRouteImport } from "./routes/gatherings/index"; +import { Route as BookingsauthRouteRouteImport } from "./routes/bookings/(auth)/route"; +import { Route as GatheringsGatheringIdIndexRouteImport } from "./routes/gatherings/$gatheringId/index"; +import { Route as GatheringsauthCreateRouteImport } from "./routes/gatherings/(auth).create"; +import { Route as BookingsauthHostingIndexRouteImport } from "./routes/bookings/(auth)/hosting/index"; +import { Route as BookingsauthAttendingIndexRouteImport } from "./routes/bookings/(auth)/attending/index"; +import { Route as GatheringsGatheringIdauthUpdateRouteImport } from "./routes/gatherings/$gatheringId/(auth).update"; +import { Route as BookingsauthHostingGatheringIdDashboardIndexRouteImport } from "./routes/bookings/(auth)/hosting/$gatheringId/dashboard.index"; +import { Route as BookingsauthHostingGatheringIdDashboardScanRouteImport } from "./routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan"; const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/", + path: "/", + getParentRoute: () => rootRouteImport +} as any); const LoginIndexRoute = LoginIndexRouteImport.update({ - id: '/login/', - path: '/login/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/login/", + path: "/login/", + getParentRoute: () => rootRouteImport +} as any); const HealthcheckIndexRoute = HealthcheckIndexRouteImport.update({ - id: '/healthcheck/', - path: '/healthcheck/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/healthcheck/", + path: "/healthcheck/", + getParentRoute: () => rootRouteImport +} as any); const GatheringsIndexRoute = GatheringsIndexRouteImport.update({ - id: '/gatherings/', - path: '/gatherings/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/gatherings/", + path: "/gatherings/", + getParentRoute: () => rootRouteImport +} as any); const BookingsauthRouteRoute = BookingsauthRouteRouteImport.update({ - id: '/bookings/(auth)', - path: '/bookings/', - getParentRoute: () => rootRouteImport, -} as any) -const GatheringsGatheringIdIndexRoute = - GatheringsGatheringIdIndexRouteImport.update({ - id: '/gatherings/$gatheringId/', - path: '/gatherings/$gatheringId/', - getParentRoute: () => rootRouteImport, - } as any) + id: "/bookings/(auth)", + path: "/bookings/", + getParentRoute: () => rootRouteImport +} as any); +const GatheringsGatheringIdIndexRoute = GatheringsGatheringIdIndexRouteImport.update({ + id: "/gatherings/$gatheringId/", + path: "/gatherings/$gatheringId/", + getParentRoute: () => rootRouteImport +} as any); const GatheringsauthCreateRoute = GatheringsauthCreateRouteImport.update({ - id: '/gatherings/(auth)/create', - path: '/gatherings/create', - getParentRoute: () => rootRouteImport, -} as any) -const BookingsauthHostingIndexRoute = - BookingsauthHostingIndexRouteImport.update({ - id: '/hosting/', - path: '/hosting/', - getParentRoute: () => BookingsauthRouteRoute, - } as any) -const BookingsauthAttendingIndexRoute = - BookingsauthAttendingIndexRouteImport.update({ - id: '/attending/', - path: '/attending/', - getParentRoute: () => BookingsauthRouteRoute, - } as any) -const GatheringsGatheringIdauthUpdateRoute = - GatheringsGatheringIdauthUpdateRouteImport.update({ - id: '/gatherings/$gatheringId/(auth)/update', - path: '/gatherings/$gatheringId/update', - getParentRoute: () => rootRouteImport, - } as any) + id: "/gatherings/(auth)/create", + path: "/gatherings/create", + getParentRoute: () => rootRouteImport +} as any); +const BookingsauthHostingIndexRoute = BookingsauthHostingIndexRouteImport.update({ + id: "/hosting/", + path: "/hosting/", + getParentRoute: () => BookingsauthRouteRoute +} as any); +const BookingsauthAttendingIndexRoute = BookingsauthAttendingIndexRouteImport.update({ + id: "/attending/", + path: "/attending/", + getParentRoute: () => BookingsauthRouteRoute +} as any); +const GatheringsGatheringIdauthUpdateRoute = GatheringsGatheringIdauthUpdateRouteImport.update({ + id: "/gatherings/$gatheringId/(auth)/update", + path: "/gatherings/$gatheringId/update", + getParentRoute: () => rootRouteImport +} as any); const BookingsauthHostingGatheringIdDashboardIndexRoute = - BookingsauthHostingGatheringIdDashboardIndexRouteImport.update({ - id: '/hosting/$gatheringId/dashboard/', - path: '/hosting/$gatheringId/dashboard/', - getParentRoute: () => BookingsauthRouteRoute, - } as any) + BookingsauthHostingGatheringIdDashboardIndexRouteImport.update({ + id: "/hosting/$gatheringId/dashboard/", + path: "/hosting/$gatheringId/dashboard/", + getParentRoute: () => BookingsauthRouteRoute + } as any); const BookingsauthHostingGatheringIdDashboardScanRoute = - BookingsauthHostingGatheringIdDashboardScanRouteImport.update({ - id: '/hosting/$gatheringId/dashboard/scan', - path: '/hosting/$gatheringId/dashboard/scan', - getParentRoute: () => BookingsauthRouteRoute, - } as any) + BookingsauthHostingGatheringIdDashboardScanRouteImport.update({ + id: "/hosting/$gatheringId/dashboard/scan", + path: "/hosting/$gatheringId/dashboard/scan", + getParentRoute: () => BookingsauthRouteRoute + } as any); export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/bookings': typeof BookingsauthRouteRouteWithChildren - '/gatherings': typeof GatheringsIndexRoute - '/healthcheck': typeof HealthcheckIndexRoute - '/login': typeof LoginIndexRoute - '/gatherings/create': typeof GatheringsauthCreateRoute - '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute - '/gatherings/$gatheringId/update': typeof GatheringsGatheringIdauthUpdateRoute - '/bookings/attending': typeof BookingsauthAttendingIndexRoute - '/bookings/hosting': typeof BookingsauthHostingIndexRoute - '/bookings/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute - '/bookings/hosting/$gatheringId/dashboard': typeof BookingsauthHostingGatheringIdDashboardIndexRoute + "/": typeof IndexRoute; + "/bookings": typeof BookingsauthRouteRouteWithChildren; + "/gatherings": typeof GatheringsIndexRoute; + "/healthcheck": typeof HealthcheckIndexRoute; + "/login": typeof LoginIndexRoute; + "/gatherings/create": typeof GatheringsauthCreateRoute; + "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; + "/gatherings/$gatheringId/update": typeof GatheringsGatheringIdauthUpdateRoute; + "/bookings/attending": typeof BookingsauthAttendingIndexRoute; + "/bookings/hosting": typeof BookingsauthHostingIndexRoute; + "/bookings/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; + "/bookings/hosting/$gatheringId/dashboard": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/bookings': typeof BookingsauthRouteRouteWithChildren - '/gatherings': typeof GatheringsIndexRoute - '/healthcheck': typeof HealthcheckIndexRoute - '/login': typeof LoginIndexRoute - '/gatherings/create': typeof GatheringsauthCreateRoute - '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute - '/gatherings/$gatheringId/update': typeof GatheringsGatheringIdauthUpdateRoute - '/bookings/attending': typeof BookingsauthAttendingIndexRoute - '/bookings/hosting': typeof BookingsauthHostingIndexRoute - '/bookings/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute - '/bookings/hosting/$gatheringId/dashboard': typeof BookingsauthHostingGatheringIdDashboardIndexRoute + "/": typeof IndexRoute; + "/bookings": typeof BookingsauthRouteRouteWithChildren; + "/gatherings": typeof GatheringsIndexRoute; + "/healthcheck": typeof HealthcheckIndexRoute; + "/login": typeof LoginIndexRoute; + "/gatherings/create": typeof GatheringsauthCreateRoute; + "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; + "/gatherings/$gatheringId/update": typeof GatheringsGatheringIdauthUpdateRoute; + "/bookings/attending": typeof BookingsauthAttendingIndexRoute; + "/bookings/hosting": typeof BookingsauthHostingIndexRoute; + "/bookings/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; + "/bookings/hosting/$gatheringId/dashboard": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/bookings/(auth)': typeof BookingsauthRouteRouteWithChildren - '/gatherings/': typeof GatheringsIndexRoute - '/healthcheck/': typeof HealthcheckIndexRoute - '/login/': typeof LoginIndexRoute - '/gatherings/(auth)/create': typeof GatheringsauthCreateRoute - '/gatherings/$gatheringId/': typeof GatheringsGatheringIdIndexRoute - '/gatherings/$gatheringId/(auth)/update': typeof GatheringsGatheringIdauthUpdateRoute - '/bookings/(auth)/attending/': typeof BookingsauthAttendingIndexRoute - '/bookings/(auth)/hosting/': typeof BookingsauthHostingIndexRoute - '/bookings/(auth)/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute - '/bookings/(auth)/hosting/$gatheringId/dashboard/': typeof BookingsauthHostingGatheringIdDashboardIndexRoute + __root__: typeof rootRouteImport; + "/": typeof IndexRoute; + "/bookings/(auth)": typeof BookingsauthRouteRouteWithChildren; + "/gatherings/": typeof GatheringsIndexRoute; + "/healthcheck/": typeof HealthcheckIndexRoute; + "/login/": typeof LoginIndexRoute; + "/gatherings/(auth)/create": typeof GatheringsauthCreateRoute; + "/gatherings/$gatheringId/": typeof GatheringsGatheringIdIndexRoute; + "/gatherings/$gatheringId/(auth)/update": typeof GatheringsGatheringIdauthUpdateRoute; + "/bookings/(auth)/attending/": typeof BookingsauthAttendingIndexRoute; + "/bookings/(auth)/hosting/": typeof BookingsauthHostingIndexRoute; + "/bookings/(auth)/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; + "/bookings/(auth)/hosting/$gatheringId/dashboard/": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/bookings' - | '/gatherings' - | '/healthcheck' - | '/login' - | '/gatherings/create' - | '/gatherings/$gatheringId' - | '/gatherings/$gatheringId/update' - | '/bookings/attending' - | '/bookings/hosting' - | '/bookings/hosting/$gatheringId/dashboard/scan' - | '/bookings/hosting/$gatheringId/dashboard' - fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/bookings' - | '/gatherings' - | '/healthcheck' - | '/login' - | '/gatherings/create' - | '/gatherings/$gatheringId' - | '/gatherings/$gatheringId/update' - | '/bookings/attending' - | '/bookings/hosting' - | '/bookings/hosting/$gatheringId/dashboard/scan' - | '/bookings/hosting/$gatheringId/dashboard' - id: - | '__root__' - | '/' - | '/bookings/(auth)' - | '/gatherings/' - | '/healthcheck/' - | '/login/' - | '/gatherings/(auth)/create' - | '/gatherings/$gatheringId/' - | '/gatherings/$gatheringId/(auth)/update' - | '/bookings/(auth)/attending/' - | '/bookings/(auth)/hosting/' - | '/bookings/(auth)/hosting/$gatheringId/dashboard/scan' - | '/bookings/(auth)/hosting/$gatheringId/dashboard/' - fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath; + fullPaths: + | "/" + | "/bookings" + | "/gatherings" + | "/healthcheck" + | "/login" + | "/gatherings/create" + | "/gatherings/$gatheringId" + | "/gatherings/$gatheringId/update" + | "/bookings/attending" + | "/bookings/hosting" + | "/bookings/hosting/$gatheringId/dashboard/scan" + | "/bookings/hosting/$gatheringId/dashboard"; + fileRoutesByTo: FileRoutesByTo; + to: + | "/" + | "/bookings" + | "/gatherings" + | "/healthcheck" + | "/login" + | "/gatherings/create" + | "/gatherings/$gatheringId" + | "/gatherings/$gatheringId/update" + | "/bookings/attending" + | "/bookings/hosting" + | "/bookings/hosting/$gatheringId/dashboard/scan" + | "/bookings/hosting/$gatheringId/dashboard"; + id: + | "__root__" + | "/" + | "/bookings/(auth)" + | "/gatherings/" + | "/healthcheck/" + | "/login/" + | "/gatherings/(auth)/create" + | "/gatherings/$gatheringId/" + | "/gatherings/$gatheringId/(auth)/update" + | "/bookings/(auth)/attending/" + | "/bookings/(auth)/hosting/" + | "/bookings/(auth)/hosting/$gatheringId/dashboard/scan" + | "/bookings/(auth)/hosting/$gatheringId/dashboard/"; + fileRoutesById: FileRoutesById; } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - BookingsauthRouteRoute: typeof BookingsauthRouteRouteWithChildren - GatheringsIndexRoute: typeof GatheringsIndexRoute - HealthcheckIndexRoute: typeof HealthcheckIndexRoute - LoginIndexRoute: typeof LoginIndexRoute - GatheringsauthCreateRoute: typeof GatheringsauthCreateRoute - GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute - GatheringsGatheringIdauthUpdateRoute: typeof GatheringsGatheringIdauthUpdateRoute + IndexRoute: typeof IndexRoute; + BookingsauthRouteRoute: typeof BookingsauthRouteRouteWithChildren; + GatheringsIndexRoute: typeof GatheringsIndexRoute; + HealthcheckIndexRoute: typeof HealthcheckIndexRoute; + LoginIndexRoute: typeof LoginIndexRoute; + GatheringsauthCreateRoute: typeof GatheringsauthCreateRoute; + GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute; + GatheringsGatheringIdauthUpdateRoute: typeof GatheringsGatheringIdauthUpdateRoute; } -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/login/': { - id: '/login/' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/healthcheck/': { - id: '/healthcheck/' - path: '/healthcheck' - fullPath: '/healthcheck' - preLoaderRoute: typeof HealthcheckIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/gatherings/': { - id: '/gatherings/' - path: '/gatherings' - fullPath: '/gatherings' - preLoaderRoute: typeof GatheringsIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/bookings/(auth)': { - id: '/bookings/(auth)' - path: '/bookings' - fullPath: '/bookings' - preLoaderRoute: typeof BookingsauthRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/gatherings/$gatheringId/': { - id: '/gatherings/$gatheringId/' - path: '/gatherings/$gatheringId' - fullPath: '/gatherings/$gatheringId' - preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/gatherings/(auth)/create': { - id: '/gatherings/(auth)/create' - path: '/gatherings/create' - fullPath: '/gatherings/create' - preLoaderRoute: typeof GatheringsauthCreateRouteImport - parentRoute: typeof rootRouteImport - } - '/bookings/(auth)/hosting/': { - id: '/bookings/(auth)/hosting/' - path: '/hosting' - fullPath: '/bookings/hosting' - preLoaderRoute: typeof BookingsauthHostingIndexRouteImport - parentRoute: typeof BookingsauthRouteRoute - } - '/bookings/(auth)/attending/': { - id: '/bookings/(auth)/attending/' - path: '/attending' - fullPath: '/bookings/attending' - preLoaderRoute: typeof BookingsauthAttendingIndexRouteImport - parentRoute: typeof BookingsauthRouteRoute - } - '/gatherings/$gatheringId/(auth)/update': { - id: '/gatherings/$gatheringId/(auth)/update' - path: '/gatherings/$gatheringId/update' - fullPath: '/gatherings/$gatheringId/update' - preLoaderRoute: typeof GatheringsGatheringIdauthUpdateRouteImport - parentRoute: typeof rootRouteImport - } - '/bookings/(auth)/hosting/$gatheringId/dashboard/': { - id: '/bookings/(auth)/hosting/$gatheringId/dashboard/' - path: '/hosting/$gatheringId/dashboard' - fullPath: '/bookings/hosting/$gatheringId/dashboard' - preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRouteImport - parentRoute: typeof BookingsauthRouteRoute - } - '/bookings/(auth)/hosting/$gatheringId/dashboard/scan': { - id: '/bookings/(auth)/hosting/$gatheringId/dashboard/scan' - path: '/hosting/$gatheringId/dashboard/scan' - fullPath: '/bookings/hosting/$gatheringId/dashboard/scan' - preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardScanRouteImport - parentRoute: typeof BookingsauthRouteRoute - } - } +declare module "@tanstack/react-router" { + interface FileRoutesByPath { + "/": { + id: "/"; + path: "/"; + fullPath: "/"; + preLoaderRoute: typeof IndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/login/": { + id: "/login/"; + path: "/login"; + fullPath: "/login"; + preLoaderRoute: typeof LoginIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/healthcheck/": { + id: "/healthcheck/"; + path: "/healthcheck"; + fullPath: "/healthcheck"; + preLoaderRoute: typeof HealthcheckIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/gatherings/": { + id: "/gatherings/"; + path: "/gatherings"; + fullPath: "/gatherings"; + preLoaderRoute: typeof GatheringsIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/bookings/(auth)": { + id: "/bookings/(auth)"; + path: "/bookings"; + fullPath: "/bookings"; + preLoaderRoute: typeof BookingsauthRouteRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/gatherings/$gatheringId/": { + id: "/gatherings/$gatheringId/"; + path: "/gatherings/$gatheringId"; + fullPath: "/gatherings/$gatheringId"; + preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/gatherings/(auth)/create": { + id: "/gatherings/(auth)/create"; + path: "/gatherings/create"; + fullPath: "/gatherings/create"; + preLoaderRoute: typeof GatheringsauthCreateRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/bookings/(auth)/hosting/": { + id: "/bookings/(auth)/hosting/"; + path: "/hosting"; + fullPath: "/bookings/hosting"; + preLoaderRoute: typeof BookingsauthHostingIndexRouteImport; + parentRoute: typeof BookingsauthRouteRoute; + }; + "/bookings/(auth)/attending/": { + id: "/bookings/(auth)/attending/"; + path: "/attending"; + fullPath: "/bookings/attending"; + preLoaderRoute: typeof BookingsauthAttendingIndexRouteImport; + parentRoute: typeof BookingsauthRouteRoute; + }; + "/gatherings/$gatheringId/(auth)/update": { + id: "/gatherings/$gatheringId/(auth)/update"; + path: "/gatherings/$gatheringId/update"; + fullPath: "/gatherings/$gatheringId/update"; + preLoaderRoute: typeof GatheringsGatheringIdauthUpdateRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/bookings/(auth)/hosting/$gatheringId/dashboard/": { + id: "/bookings/(auth)/hosting/$gatheringId/dashboard/"; + path: "/hosting/$gatheringId/dashboard"; + fullPath: "/bookings/hosting/$gatheringId/dashboard"; + preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRouteImport; + parentRoute: typeof BookingsauthRouteRoute; + }; + "/bookings/(auth)/hosting/$gatheringId/dashboard/scan": { + id: "/bookings/(auth)/hosting/$gatheringId/dashboard/scan"; + path: "/hosting/$gatheringId/dashboard/scan"; + fullPath: "/bookings/hosting/$gatheringId/dashboard/scan"; + preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardScanRouteImport; + parentRoute: typeof BookingsauthRouteRoute; + }; + } } interface BookingsauthRouteRouteChildren { - BookingsauthAttendingIndexRoute: typeof BookingsauthAttendingIndexRoute - BookingsauthHostingIndexRoute: typeof BookingsauthHostingIndexRoute - BookingsauthHostingGatheringIdDashboardScanRoute: typeof BookingsauthHostingGatheringIdDashboardScanRoute - BookingsauthHostingGatheringIdDashboardIndexRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRoute + BookingsauthAttendingIndexRoute: typeof BookingsauthAttendingIndexRoute; + BookingsauthHostingIndexRoute: typeof BookingsauthHostingIndexRoute; + BookingsauthHostingGatheringIdDashboardScanRoute: typeof BookingsauthHostingGatheringIdDashboardScanRoute; + BookingsauthHostingGatheringIdDashboardIndexRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRoute; } const BookingsauthRouteRouteChildren: BookingsauthRouteRouteChildren = { - BookingsauthAttendingIndexRoute: BookingsauthAttendingIndexRoute, - BookingsauthHostingIndexRoute: BookingsauthHostingIndexRoute, - BookingsauthHostingGatheringIdDashboardScanRoute: - BookingsauthHostingGatheringIdDashboardScanRoute, - BookingsauthHostingGatheringIdDashboardIndexRoute: - BookingsauthHostingGatheringIdDashboardIndexRoute, -} + BookingsauthAttendingIndexRoute: BookingsauthAttendingIndexRoute, + BookingsauthHostingIndexRoute: BookingsauthHostingIndexRoute, + BookingsauthHostingGatheringIdDashboardScanRoute: + BookingsauthHostingGatheringIdDashboardScanRoute, + BookingsauthHostingGatheringIdDashboardIndexRoute: + BookingsauthHostingGatheringIdDashboardIndexRoute +}; -const BookingsauthRouteRouteWithChildren = - BookingsauthRouteRoute._addFileChildren(BookingsauthRouteRouteChildren) +const BookingsauthRouteRouteWithChildren = BookingsauthRouteRoute._addFileChildren( + BookingsauthRouteRouteChildren +); const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - BookingsauthRouteRoute: BookingsauthRouteRouteWithChildren, - GatheringsIndexRoute: GatheringsIndexRoute, - HealthcheckIndexRoute: HealthcheckIndexRoute, - LoginIndexRoute: LoginIndexRoute, - GatheringsauthCreateRoute: GatheringsauthCreateRoute, - GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, - GatheringsGatheringIdauthUpdateRoute: GatheringsGatheringIdauthUpdateRoute, -} + IndexRoute: IndexRoute, + BookingsauthRouteRoute: BookingsauthRouteRouteWithChildren, + GatheringsIndexRoute: GatheringsIndexRoute, + HealthcheckIndexRoute: HealthcheckIndexRoute, + LoginIndexRoute: LoginIndexRoute, + GatheringsauthCreateRoute: GatheringsauthCreateRoute, + GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, + GatheringsGatheringIdauthUpdateRoute: GatheringsGatheringIdauthUpdateRoute +}; export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() + ._addFileChildren(rootRouteChildren) + ._addFileTypes(); diff --git a/src/evently.client/src/routes/__root.tsx b/src/evently.client/src/routes/__root.tsx index 62b91c1..68a339d 100644 --- a/src/evently.client/src/routes/__root.tsx +++ b/src/evently.client/src/routes/__root.tsx @@ -3,8 +3,8 @@ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { Navbar, NotFound } from "~/lib/components"; import { type JSX, useEffect } from "react"; import { getAccount } from "~/lib/services"; -import { Account } from "~/lib/domains/entities"; -import type { RouteContext } from "~/lib/domains/interfaces/route-context.ts"; +import { Account } from "~/domains/entities"; +import type { RouteContext } from "~/domains/interfaces/route-context.ts"; import polyfill from "@oddbird/css-anchor-positioning/fn"; export const Route = createRootRouteWithContext()({ diff --git a/src/evently.client/src/routes/bookings/(auth)/attending/index.tsx b/src/evently.client/src/routes/bookings/(auth)/attending/index.tsx index 520c60b..e1a7fa7 100644 --- a/src/evently.client/src/routes/bookings/(auth)/attending/index.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/attending/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { type JSX, useState } from "react"; -import { Booking, Gathering } from "~/lib/domains/entities"; +import { Booking, Gathering } from "~/domains/entities"; import { getBookings, type GetBookingsParams } from "~/lib/services"; import { Card, Tabs, TabState } from "~/lib/components"; import cloneDeep from "lodash.clonedeep"; diff --git a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/bookings-table.tsx b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/bookings-table.tsx index a342389..cee8f34 100644 --- a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/bookings-table.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/bookings-table.tsx @@ -1,5 +1,5 @@ import type { JSX } from "react"; -import { Booking } from "~/lib/domains/entities"; +import { Booking } from "~/domains/entities"; import { toIsoDateTimeString } from "~/lib/services"; import { Icon } from "@iconify/react"; diff --git a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/jumbotron.tsx b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/jumbotron.tsx index 7078794..fd6f2bd 100644 --- a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/jumbotron.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/-components/jumbotron.tsx @@ -1,7 +1,7 @@ import { Icon } from "@iconify/react/dist/iconify.js"; import { DateTime } from "luxon"; import type { JSX } from "react"; -import { Gathering } from "~/lib/domains/entities"; +import { Gathering } from "~/domains/entities"; interface JumbotronProps { gathering: Gathering; diff --git a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.index.tsx b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.index.tsx index 02088ca..fd35ba6 100644 --- a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.index.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.index.tsx @@ -1,4 +1,4 @@ -import { Booking, Gathering } from "~/lib/domains/entities"; +import { Booking, Gathering } from "~/domains/entities"; import { downloadFile, getBookings, @@ -15,7 +15,7 @@ import { json2csv } from "json-2-csv"; import { useQuery } from "@tanstack/react-query"; import { BookingsTable, Jumbotron, StatsCard } from "./-components"; import { useInterval } from "usehooks-ts"; -import type { PageResult } from "~/lib/domains/interfaces"; +import type { PageResult } from "~/domains/interfaces"; export const Route = createFileRoute("/bookings/(auth)/hosting/$gatheringId/dashboard/")({ loader: async ({ params }) => { diff --git a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan.tsx b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan.tsx index 6ca4943..32b8a31 100644 --- a/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; import { Scanner } from "./-components"; import { checkInBooking, sleep } from "~/lib/services"; -import { Booking } from "~/lib/domains/entities"; +import { Booking } from "~/domains/entities"; import { useCallback, useState } from "react"; -import { ToastContent, ToastStatus, toastStyles } from "~/lib/domains/models"; +import { ToastContent, ToastStatus, toastStyles } from "~/domains/models"; import { useForm } from "@tanstack/react-form"; import { FieldErrMsg as FieldInfo } from "~/lib/components"; diff --git a/src/evently.client/src/routes/bookings/(auth)/hosting/index.tsx b/src/evently.client/src/routes/bookings/(auth)/hosting/index.tsx index e788c6e..1cbe4d4 100644 --- a/src/evently.client/src/routes/bookings/(auth)/hosting/index.tsx +++ b/src/evently.client/src/routes/bookings/(auth)/hosting/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { type JSX, useState } from "react"; -import { Gathering } from "~/lib/domains/entities"; +import { Gathering } from "~/domains/entities"; import { getGatherings, type GetGatheringsParams } from "~/lib/services"; import { Card, Tabs, TabState } from "~/lib/components"; import { useQuery } from "@tanstack/react-query"; diff --git a/src/evently.client/src/routes/gatherings/$gatheringId/(auth).update.tsx b/src/evently.client/src/routes/gatherings/$gatheringId/(auth).update.tsx index 8f14b83..e7e1589 100644 --- a/src/evently.client/src/routes/gatherings/$gatheringId/(auth).update.tsx +++ b/src/evently.client/src/routes/gatherings/$gatheringId/(auth).update.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Category, Gathering } from "~/lib/domains/entities"; +import { Category, Gathering } from "~/domains/entities"; import { type JSX, useEffect, useState } from "react"; import { authenticateRoute, @@ -13,7 +13,7 @@ import { type GatheringForm as IGatheringForm, useGatheringForm } from "~/routes/gatherings/-services"; -import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; +import { GatheringReqDto, ToastContent } from "~/domains/models"; import { GatheringForm } from "~/routes/gatherings/-components"; export const Route = createFileRoute("/gatherings/$gatheringId/(auth)/update")({ diff --git a/src/evently.client/src/routes/gatherings/$gatheringId/-components/jumbotron.tsx b/src/evently.client/src/routes/gatherings/$gatheringId/-components/jumbotron.tsx index 2ad3ee0..0543c86 100644 --- a/src/evently.client/src/routes/gatherings/$gatheringId/-components/jumbotron.tsx +++ b/src/evently.client/src/routes/gatherings/$gatheringId/-components/jumbotron.tsx @@ -1,6 +1,6 @@ import type { JSX } from "react"; import { Icon } from "@iconify/react"; -import { Booking, Category, Gathering } from "~/lib/domains/entities"; +import { Booking, Category, Gathering } from "~/domains/entities"; import { DateTime } from "luxon"; interface JumbotronProps { diff --git a/src/evently.client/src/routes/gatherings/$gatheringId/-components/qr-dialog.tsx b/src/evently.client/src/routes/gatherings/$gatheringId/-components/qr-dialog.tsx index 87f3263..b3fe324 100644 --- a/src/evently.client/src/routes/gatherings/$gatheringId/-components/qr-dialog.tsx +++ b/src/evently.client/src/routes/gatherings/$gatheringId/-components/qr-dialog.tsx @@ -1,5 +1,5 @@ import { type JSX, type Ref, useEffect, useRef } from "react"; -import { Booking } from "~/lib/domains/entities"; +import { Booking } from "~/domains/entities"; import QRCode from "qrcode"; // 1. Define the props for the child component diff --git a/src/evently.client/src/routes/gatherings/$gatheringId/index.tsx b/src/evently.client/src/routes/gatherings/$gatheringId/index.tsx index 3ba6d64..3589f60 100644 --- a/src/evently.client/src/routes/gatherings/$gatheringId/index.tsx +++ b/src/evently.client/src/routes/gatherings/$gatheringId/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { type JSX, useRef } from "react"; -import { Booking, Gathering } from "~/lib/domains/entities"; +import { Booking, Gathering } from "~/domains/entities"; import { cancelBooking, createBooking, @@ -10,7 +10,7 @@ import { updateGathering } from "~/lib/services"; import { useMutation } from "@tanstack/react-query"; -import { BookingReqDto, GatheringReqDto } from "~/lib/domains/models"; +import { BookingReqDto, GatheringReqDto } from "~/domains/models"; import { CancellationDialog, Jumbotron, QrDialog } from "./-components"; import Placeholder1 from "~/lib/assets/event_placeholder_1.webp"; import Placeholder2 from "~/lib/assets/event_placeholder_2.png"; diff --git a/src/evently.client/src/routes/gatherings/-components/filter-bar.tsx b/src/evently.client/src/routes/gatherings/-components/filter-bar.tsx index 2c22ac9..e6b0dc2 100644 --- a/src/evently.client/src/routes/gatherings/-components/filter-bar.tsx +++ b/src/evently.client/src/routes/gatherings/-components/filter-bar.tsx @@ -1,6 +1,6 @@ import React, { type JSX } from "react"; import { type GetGatheringsParams, toIsoDateString } from "~/lib/services"; -import { Category } from "~/lib/domains/entities"; +import { Category } from "~/domains/entities"; import { DateTime } from "luxon"; import { Icon } from "@iconify/react"; diff --git a/src/evently.client/src/routes/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/gatherings/-components/gathering-form.tsx index f702e17..3ec7b60 100644 --- a/src/evently.client/src/routes/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/gatherings/-components/gathering-form.tsx @@ -3,10 +3,10 @@ import { compressImage, type GatheringForm as IGatheringForm } from "../-service import { FieldErrMsg as FieldInfo } from "~/lib/components"; import { Icon } from "@iconify/react"; import { DateTime } from "luxon"; -import { GatheringCategoryDetailReqDto, GatheringReqDto, ToastContent } from "~/lib/domains/models"; +import { GatheringCategoryDetailReqDto, GatheringReqDto, ToastContent } from "~/domains/models"; import { useRouter } from "@tanstack/react-router"; import { toIsoDateTimeString } from "~/lib/services"; -import { Category } from "~/lib/domains/entities"; +import { Category } from "~/domains/entities"; interface GatheringFormProps { file: File | null; diff --git a/src/evently.client/src/routes/gatherings/-services/use-gathering-form.ts b/src/evently.client/src/routes/gatherings/-services/use-gathering-form.ts index 48a693a..d92f7ba 100644 --- a/src/evently.client/src/routes/gatherings/-services/use-gathering-form.ts +++ b/src/evently.client/src/routes/gatherings/-services/use-gathering-form.ts @@ -1,5 +1,5 @@ import { useForm } from "@tanstack/react-form"; -import { GatheringReqDto } from "~/lib/domains/models"; +import { GatheringReqDto } from "~/domains/models"; export function useGatheringForm( defaultGathering: GatheringReqDto, diff --git a/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs b/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs index 281acca..c6de26a 100644 --- a/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs +++ b/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs @@ -1,6 +1,6 @@ -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Models; -using Evently.Server.Common.Extensions; +using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Models; namespace Evently.Server.Test.Common.Extensions; diff --git a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs index 45313e9..c944538 100644 --- a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs @@ -1,7 +1,8 @@ using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Data; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Evently.Server.Features.Bookings.Services; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs index 655c196..fd0fa9a 100644 --- a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs @@ -1,7 +1,8 @@ using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Domains.Entities; -using Evently.Server.Common.Domains.Interfaces; -using Evently.Server.Common.Domains.Models; +using Evently.Server.Common.Data; +using Evently.Server.Domains.Entities; +using Evently.Server.Domains.Interfaces; +using Evently.Server.Domains.Models; using Evently.Server.Features.Gatherings.Services; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; From 78b2a4d8eb16b1f07fae12d7a213fff782245873 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:04:39 +0800 Subject: [PATCH 02/10] chore: update --- ...250927053802_UpdateSeededDates.Designer.cs | 1 - .../Bookings/Services/BookingService.cs | 1 - .../Categories/Services/CategoryService.cs | 3 +- .../Gatherings/Services/GatheringService.cs | 3 +- src/evently.client/src/routeTree.gen.ts | 537 +++++++++--------- .../src/routes/gatherings/(auth).create.tsx | 4 +- .../src/routes/gatherings/index.tsx | 4 +- .../Bookings/Services/BookingServiceTests.cs | 3 +- .../Services/GatheringServiceTests.cs | 3 +- 9 files changed, 278 insertions(+), 281 deletions(-) diff --git a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs index 781f997..d005d21 100644 --- a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.Designer.cs @@ -1,6 +1,5 @@ // using System; -using Evently.Server.Common.Adapters.Data; using Evently.Server.Common.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index de9be6b..e26bec2 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -1,4 +1,3 @@ -using Evently.Server.Common.Adapters.Data; using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; diff --git a/src/Evently.Server/Features/Categories/Services/CategoryService.cs b/src/Evently.Server/Features/Categories/Services/CategoryService.cs index fd78105..6258dba 100644 --- a/src/Evently.Server/Features/Categories/Services/CategoryService.cs +++ b/src/Evently.Server/Features/Categories/Services/CategoryService.cs @@ -1,5 +1,4 @@ -using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Data; +using Evently.Server.Common.Data; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index bf80217..4509024 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -1,5 +1,4 @@ -using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Data; +using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; diff --git a/src/evently.client/src/routeTree.gen.ts b/src/evently.client/src/routeTree.gen.ts index 2b5ff9a..038453e 100644 --- a/src/evently.client/src/routeTree.gen.ts +++ b/src/evently.client/src/routeTree.gen.ts @@ -8,301 +8,304 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from "./routes/__root"; -import { Route as IndexRouteImport } from "./routes/index"; -import { Route as LoginIndexRouteImport } from "./routes/login/index"; -import { Route as HealthcheckIndexRouteImport } from "./routes/healthcheck/index"; -import { Route as GatheringsIndexRouteImport } from "./routes/gatherings/index"; -import { Route as BookingsauthRouteRouteImport } from "./routes/bookings/(auth)/route"; -import { Route as GatheringsGatheringIdIndexRouteImport } from "./routes/gatherings/$gatheringId/index"; -import { Route as GatheringsauthCreateRouteImport } from "./routes/gatherings/(auth).create"; -import { Route as BookingsauthHostingIndexRouteImport } from "./routes/bookings/(auth)/hosting/index"; -import { Route as BookingsauthAttendingIndexRouteImport } from "./routes/bookings/(auth)/attending/index"; -import { Route as GatheringsGatheringIdauthUpdateRouteImport } from "./routes/gatherings/$gatheringId/(auth).update"; -import { Route as BookingsauthHostingGatheringIdDashboardIndexRouteImport } from "./routes/bookings/(auth)/hosting/$gatheringId/dashboard.index"; -import { Route as BookingsauthHostingGatheringIdDashboardScanRouteImport } from "./routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan"; +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as LoginIndexRouteImport } from './routes/login/index' +import { Route as HealthcheckIndexRouteImport } from './routes/healthcheck/index' +import { Route as GatheringsIndexRouteImport } from './routes/gatherings/index' +import { Route as BookingsauthRouteRouteImport } from './routes/bookings/(auth)/route' +import { Route as GatheringsGatheringIdIndexRouteImport } from './routes/gatherings/$gatheringId/index' +import { Route as GatheringsauthCreateRouteImport } from './routes/gatherings/(auth).create' +import { Route as BookingsauthHostingIndexRouteImport } from './routes/bookings/(auth)/hosting/index' +import { Route as BookingsauthAttendingIndexRouteImport } from './routes/bookings/(auth)/attending/index' +import { Route as GatheringsGatheringIdauthUpdateRouteImport } from './routes/gatherings/$gatheringId/(auth).update' +import { Route as BookingsauthHostingGatheringIdDashboardIndexRouteImport } from './routes/bookings/(auth)/hosting/$gatheringId/dashboard.index' +import { Route as BookingsauthHostingGatheringIdDashboardScanRouteImport } from './routes/bookings/(auth)/hosting/$gatheringId/dashboard.scan' const IndexRoute = IndexRouteImport.update({ - id: "/", - path: "/", - getParentRoute: () => rootRouteImport -} as any); + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const LoginIndexRoute = LoginIndexRouteImport.update({ - id: "/login/", - path: "/login/", - getParentRoute: () => rootRouteImport -} as any); + id: '/login/', + path: '/login/', + getParentRoute: () => rootRouteImport, +} as any) const HealthcheckIndexRoute = HealthcheckIndexRouteImport.update({ - id: "/healthcheck/", - path: "/healthcheck/", - getParentRoute: () => rootRouteImport -} as any); + id: '/healthcheck/', + path: '/healthcheck/', + getParentRoute: () => rootRouteImport, +} as any) const GatheringsIndexRoute = GatheringsIndexRouteImport.update({ - id: "/gatherings/", - path: "/gatherings/", - getParentRoute: () => rootRouteImport -} as any); + id: '/gatherings/', + path: '/gatherings/', + getParentRoute: () => rootRouteImport, +} as any) const BookingsauthRouteRoute = BookingsauthRouteRouteImport.update({ - id: "/bookings/(auth)", - path: "/bookings/", - getParentRoute: () => rootRouteImport -} as any); -const GatheringsGatheringIdIndexRoute = GatheringsGatheringIdIndexRouteImport.update({ - id: "/gatherings/$gatheringId/", - path: "/gatherings/$gatheringId/", - getParentRoute: () => rootRouteImport -} as any); + id: '/bookings/(auth)', + path: '/bookings/', + getParentRoute: () => rootRouteImport, +} as any) +const GatheringsGatheringIdIndexRoute = + GatheringsGatheringIdIndexRouteImport.update({ + id: '/gatherings/$gatheringId/', + path: '/gatherings/$gatheringId/', + getParentRoute: () => rootRouteImport, + } as any) const GatheringsauthCreateRoute = GatheringsauthCreateRouteImport.update({ - id: "/gatherings/(auth)/create", - path: "/gatherings/create", - getParentRoute: () => rootRouteImport -} as any); -const BookingsauthHostingIndexRoute = BookingsauthHostingIndexRouteImport.update({ - id: "/hosting/", - path: "/hosting/", - getParentRoute: () => BookingsauthRouteRoute -} as any); -const BookingsauthAttendingIndexRoute = BookingsauthAttendingIndexRouteImport.update({ - id: "/attending/", - path: "/attending/", - getParentRoute: () => BookingsauthRouteRoute -} as any); -const GatheringsGatheringIdauthUpdateRoute = GatheringsGatheringIdauthUpdateRouteImport.update({ - id: "/gatherings/$gatheringId/(auth)/update", - path: "/gatherings/$gatheringId/update", - getParentRoute: () => rootRouteImport -} as any); + id: '/gatherings/(auth)/create', + path: '/gatherings/create', + getParentRoute: () => rootRouteImport, +} as any) +const BookingsauthHostingIndexRoute = + BookingsauthHostingIndexRouteImport.update({ + id: '/hosting/', + path: '/hosting/', + getParentRoute: () => BookingsauthRouteRoute, + } as any) +const BookingsauthAttendingIndexRoute = + BookingsauthAttendingIndexRouteImport.update({ + id: '/attending/', + path: '/attending/', + getParentRoute: () => BookingsauthRouteRoute, + } as any) +const GatheringsGatheringIdauthUpdateRoute = + GatheringsGatheringIdauthUpdateRouteImport.update({ + id: '/gatherings/$gatheringId/(auth)/update', + path: '/gatherings/$gatheringId/update', + getParentRoute: () => rootRouteImport, + } as any) const BookingsauthHostingGatheringIdDashboardIndexRoute = - BookingsauthHostingGatheringIdDashboardIndexRouteImport.update({ - id: "/hosting/$gatheringId/dashboard/", - path: "/hosting/$gatheringId/dashboard/", - getParentRoute: () => BookingsauthRouteRoute - } as any); + BookingsauthHostingGatheringIdDashboardIndexRouteImport.update({ + id: '/hosting/$gatheringId/dashboard/', + path: '/hosting/$gatheringId/dashboard/', + getParentRoute: () => BookingsauthRouteRoute, + } as any) const BookingsauthHostingGatheringIdDashboardScanRoute = - BookingsauthHostingGatheringIdDashboardScanRouteImport.update({ - id: "/hosting/$gatheringId/dashboard/scan", - path: "/hosting/$gatheringId/dashboard/scan", - getParentRoute: () => BookingsauthRouteRoute - } as any); + BookingsauthHostingGatheringIdDashboardScanRouteImport.update({ + id: '/hosting/$gatheringId/dashboard/scan', + path: '/hosting/$gatheringId/dashboard/scan', + getParentRoute: () => BookingsauthRouteRoute, + } as any) export interface FileRoutesByFullPath { - "/": typeof IndexRoute; - "/bookings": typeof BookingsauthRouteRouteWithChildren; - "/gatherings": typeof GatheringsIndexRoute; - "/healthcheck": typeof HealthcheckIndexRoute; - "/login": typeof LoginIndexRoute; - "/gatherings/create": typeof GatheringsauthCreateRoute; - "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; - "/gatherings/$gatheringId/update": typeof GatheringsGatheringIdauthUpdateRoute; - "/bookings/attending": typeof BookingsauthAttendingIndexRoute; - "/bookings/hosting": typeof BookingsauthHostingIndexRoute; - "/bookings/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; - "/bookings/hosting/$gatheringId/dashboard": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; + '/': typeof IndexRoute + '/bookings': typeof BookingsauthRouteRouteWithChildren + '/gatherings': typeof GatheringsIndexRoute + '/healthcheck': typeof HealthcheckIndexRoute + '/login': typeof LoginIndexRoute + '/gatherings/create': typeof GatheringsauthCreateRoute + '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute + '/gatherings/$gatheringId/update': typeof GatheringsGatheringIdauthUpdateRoute + '/bookings/attending': typeof BookingsauthAttendingIndexRoute + '/bookings/hosting': typeof BookingsauthHostingIndexRoute + '/bookings/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute + '/bookings/hosting/$gatheringId/dashboard': typeof BookingsauthHostingGatheringIdDashboardIndexRoute } export interface FileRoutesByTo { - "/": typeof IndexRoute; - "/bookings": typeof BookingsauthRouteRouteWithChildren; - "/gatherings": typeof GatheringsIndexRoute; - "/healthcheck": typeof HealthcheckIndexRoute; - "/login": typeof LoginIndexRoute; - "/gatherings/create": typeof GatheringsauthCreateRoute; - "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; - "/gatherings/$gatheringId/update": typeof GatheringsGatheringIdauthUpdateRoute; - "/bookings/attending": typeof BookingsauthAttendingIndexRoute; - "/bookings/hosting": typeof BookingsauthHostingIndexRoute; - "/bookings/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; - "/bookings/hosting/$gatheringId/dashboard": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; + '/': typeof IndexRoute + '/bookings': typeof BookingsauthRouteRouteWithChildren + '/gatherings': typeof GatheringsIndexRoute + '/healthcheck': typeof HealthcheckIndexRoute + '/login': typeof LoginIndexRoute + '/gatherings/create': typeof GatheringsauthCreateRoute + '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute + '/gatherings/$gatheringId/update': typeof GatheringsGatheringIdauthUpdateRoute + '/bookings/attending': typeof BookingsauthAttendingIndexRoute + '/bookings/hosting': typeof BookingsauthHostingIndexRoute + '/bookings/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute + '/bookings/hosting/$gatheringId/dashboard': typeof BookingsauthHostingGatheringIdDashboardIndexRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport; - "/": typeof IndexRoute; - "/bookings/(auth)": typeof BookingsauthRouteRouteWithChildren; - "/gatherings/": typeof GatheringsIndexRoute; - "/healthcheck/": typeof HealthcheckIndexRoute; - "/login/": typeof LoginIndexRoute; - "/gatherings/(auth)/create": typeof GatheringsauthCreateRoute; - "/gatherings/$gatheringId/": typeof GatheringsGatheringIdIndexRoute; - "/gatherings/$gatheringId/(auth)/update": typeof GatheringsGatheringIdauthUpdateRoute; - "/bookings/(auth)/attending/": typeof BookingsauthAttendingIndexRoute; - "/bookings/(auth)/hosting/": typeof BookingsauthHostingIndexRoute; - "/bookings/(auth)/hosting/$gatheringId/dashboard/scan": typeof BookingsauthHostingGatheringIdDashboardScanRoute; - "/bookings/(auth)/hosting/$gatheringId/dashboard/": typeof BookingsauthHostingGatheringIdDashboardIndexRoute; + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/bookings/(auth)': typeof BookingsauthRouteRouteWithChildren + '/gatherings/': typeof GatheringsIndexRoute + '/healthcheck/': typeof HealthcheckIndexRoute + '/login/': typeof LoginIndexRoute + '/gatherings/(auth)/create': typeof GatheringsauthCreateRoute + '/gatherings/$gatheringId/': typeof GatheringsGatheringIdIndexRoute + '/gatherings/$gatheringId/(auth)/update': typeof GatheringsGatheringIdauthUpdateRoute + '/bookings/(auth)/attending/': typeof BookingsauthAttendingIndexRoute + '/bookings/(auth)/hosting/': typeof BookingsauthHostingIndexRoute + '/bookings/(auth)/hosting/$gatheringId/dashboard/scan': typeof BookingsauthHostingGatheringIdDashboardScanRoute + '/bookings/(auth)/hosting/$gatheringId/dashboard/': typeof BookingsauthHostingGatheringIdDashboardIndexRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: - | "/" - | "/bookings" - | "/gatherings" - | "/healthcheck" - | "/login" - | "/gatherings/create" - | "/gatherings/$gatheringId" - | "/gatherings/$gatheringId/update" - | "/bookings/attending" - | "/bookings/hosting" - | "/bookings/hosting/$gatheringId/dashboard/scan" - | "/bookings/hosting/$gatheringId/dashboard"; - fileRoutesByTo: FileRoutesByTo; - to: - | "/" - | "/bookings" - | "/gatherings" - | "/healthcheck" - | "/login" - | "/gatherings/create" - | "/gatherings/$gatheringId" - | "/gatherings/$gatheringId/update" - | "/bookings/attending" - | "/bookings/hosting" - | "/bookings/hosting/$gatheringId/dashboard/scan" - | "/bookings/hosting/$gatheringId/dashboard"; - id: - | "__root__" - | "/" - | "/bookings/(auth)" - | "/gatherings/" - | "/healthcheck/" - | "/login/" - | "/gatherings/(auth)/create" - | "/gatherings/$gatheringId/" - | "/gatherings/$gatheringId/(auth)/update" - | "/bookings/(auth)/attending/" - | "/bookings/(auth)/hosting/" - | "/bookings/(auth)/hosting/$gatheringId/dashboard/scan" - | "/bookings/(auth)/hosting/$gatheringId/dashboard/"; - fileRoutesById: FileRoutesById; + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/bookings' + | '/gatherings' + | '/healthcheck' + | '/login' + | '/gatherings/create' + | '/gatherings/$gatheringId' + | '/gatherings/$gatheringId/update' + | '/bookings/attending' + | '/bookings/hosting' + | '/bookings/hosting/$gatheringId/dashboard/scan' + | '/bookings/hosting/$gatheringId/dashboard' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/bookings' + | '/gatherings' + | '/healthcheck' + | '/login' + | '/gatherings/create' + | '/gatherings/$gatheringId' + | '/gatherings/$gatheringId/update' + | '/bookings/attending' + | '/bookings/hosting' + | '/bookings/hosting/$gatheringId/dashboard/scan' + | '/bookings/hosting/$gatheringId/dashboard' + id: + | '__root__' + | '/' + | '/bookings/(auth)' + | '/gatherings/' + | '/healthcheck/' + | '/login/' + | '/gatherings/(auth)/create' + | '/gatherings/$gatheringId/' + | '/gatherings/$gatheringId/(auth)/update' + | '/bookings/(auth)/attending/' + | '/bookings/(auth)/hosting/' + | '/bookings/(auth)/hosting/$gatheringId/dashboard/scan' + | '/bookings/(auth)/hosting/$gatheringId/dashboard/' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute; - BookingsauthRouteRoute: typeof BookingsauthRouteRouteWithChildren; - GatheringsIndexRoute: typeof GatheringsIndexRoute; - HealthcheckIndexRoute: typeof HealthcheckIndexRoute; - LoginIndexRoute: typeof LoginIndexRoute; - GatheringsauthCreateRoute: typeof GatheringsauthCreateRoute; - GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute; - GatheringsGatheringIdauthUpdateRoute: typeof GatheringsGatheringIdauthUpdateRoute; + IndexRoute: typeof IndexRoute + BookingsauthRouteRoute: typeof BookingsauthRouteRouteWithChildren + GatheringsIndexRoute: typeof GatheringsIndexRoute + HealthcheckIndexRoute: typeof HealthcheckIndexRoute + LoginIndexRoute: typeof LoginIndexRoute + GatheringsauthCreateRoute: typeof GatheringsauthCreateRoute + GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute + GatheringsGatheringIdauthUpdateRoute: typeof GatheringsGatheringIdauthUpdateRoute } -declare module "@tanstack/react-router" { - interface FileRoutesByPath { - "/": { - id: "/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof IndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/login/": { - id: "/login/"; - path: "/login"; - fullPath: "/login"; - preLoaderRoute: typeof LoginIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/healthcheck/": { - id: "/healthcheck/"; - path: "/healthcheck"; - fullPath: "/healthcheck"; - preLoaderRoute: typeof HealthcheckIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/gatherings/": { - id: "/gatherings/"; - path: "/gatherings"; - fullPath: "/gatherings"; - preLoaderRoute: typeof GatheringsIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/bookings/(auth)": { - id: "/bookings/(auth)"; - path: "/bookings"; - fullPath: "/bookings"; - preLoaderRoute: typeof BookingsauthRouteRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/gatherings/$gatheringId/": { - id: "/gatherings/$gatheringId/"; - path: "/gatherings/$gatheringId"; - fullPath: "/gatherings/$gatheringId"; - preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/gatherings/(auth)/create": { - id: "/gatherings/(auth)/create"; - path: "/gatherings/create"; - fullPath: "/gatherings/create"; - preLoaderRoute: typeof GatheringsauthCreateRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/bookings/(auth)/hosting/": { - id: "/bookings/(auth)/hosting/"; - path: "/hosting"; - fullPath: "/bookings/hosting"; - preLoaderRoute: typeof BookingsauthHostingIndexRouteImport; - parentRoute: typeof BookingsauthRouteRoute; - }; - "/bookings/(auth)/attending/": { - id: "/bookings/(auth)/attending/"; - path: "/attending"; - fullPath: "/bookings/attending"; - preLoaderRoute: typeof BookingsauthAttendingIndexRouteImport; - parentRoute: typeof BookingsauthRouteRoute; - }; - "/gatherings/$gatheringId/(auth)/update": { - id: "/gatherings/$gatheringId/(auth)/update"; - path: "/gatherings/$gatheringId/update"; - fullPath: "/gatherings/$gatheringId/update"; - preLoaderRoute: typeof GatheringsGatheringIdauthUpdateRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/bookings/(auth)/hosting/$gatheringId/dashboard/": { - id: "/bookings/(auth)/hosting/$gatheringId/dashboard/"; - path: "/hosting/$gatheringId/dashboard"; - fullPath: "/bookings/hosting/$gatheringId/dashboard"; - preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRouteImport; - parentRoute: typeof BookingsauthRouteRoute; - }; - "/bookings/(auth)/hosting/$gatheringId/dashboard/scan": { - id: "/bookings/(auth)/hosting/$gatheringId/dashboard/scan"; - path: "/hosting/$gatheringId/dashboard/scan"; - fullPath: "/bookings/hosting/$gatheringId/dashboard/scan"; - preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardScanRouteImport; - parentRoute: typeof BookingsauthRouteRoute; - }; - } +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/login/': { + id: '/login/' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/healthcheck/': { + id: '/healthcheck/' + path: '/healthcheck' + fullPath: '/healthcheck' + preLoaderRoute: typeof HealthcheckIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/gatherings/': { + id: '/gatherings/' + path: '/gatherings' + fullPath: '/gatherings' + preLoaderRoute: typeof GatheringsIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/bookings/(auth)': { + id: '/bookings/(auth)' + path: '/bookings' + fullPath: '/bookings' + preLoaderRoute: typeof BookingsauthRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/gatherings/$gatheringId/': { + id: '/gatherings/$gatheringId/' + path: '/gatherings/$gatheringId' + fullPath: '/gatherings/$gatheringId' + preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/gatherings/(auth)/create': { + id: '/gatherings/(auth)/create' + path: '/gatherings/create' + fullPath: '/gatherings/create' + preLoaderRoute: typeof GatheringsauthCreateRouteImport + parentRoute: typeof rootRouteImport + } + '/bookings/(auth)/hosting/': { + id: '/bookings/(auth)/hosting/' + path: '/hosting' + fullPath: '/bookings/hosting' + preLoaderRoute: typeof BookingsauthHostingIndexRouteImport + parentRoute: typeof BookingsauthRouteRoute + } + '/bookings/(auth)/attending/': { + id: '/bookings/(auth)/attending/' + path: '/attending' + fullPath: '/bookings/attending' + preLoaderRoute: typeof BookingsauthAttendingIndexRouteImport + parentRoute: typeof BookingsauthRouteRoute + } + '/gatherings/$gatheringId/(auth)/update': { + id: '/gatherings/$gatheringId/(auth)/update' + path: '/gatherings/$gatheringId/update' + fullPath: '/gatherings/$gatheringId/update' + preLoaderRoute: typeof GatheringsGatheringIdauthUpdateRouteImport + parentRoute: typeof rootRouteImport + } + '/bookings/(auth)/hosting/$gatheringId/dashboard/': { + id: '/bookings/(auth)/hosting/$gatheringId/dashboard/' + path: '/hosting/$gatheringId/dashboard' + fullPath: '/bookings/hosting/$gatheringId/dashboard' + preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRouteImport + parentRoute: typeof BookingsauthRouteRoute + } + '/bookings/(auth)/hosting/$gatheringId/dashboard/scan': { + id: '/bookings/(auth)/hosting/$gatheringId/dashboard/scan' + path: '/hosting/$gatheringId/dashboard/scan' + fullPath: '/bookings/hosting/$gatheringId/dashboard/scan' + preLoaderRoute: typeof BookingsauthHostingGatheringIdDashboardScanRouteImport + parentRoute: typeof BookingsauthRouteRoute + } + } } interface BookingsauthRouteRouteChildren { - BookingsauthAttendingIndexRoute: typeof BookingsauthAttendingIndexRoute; - BookingsauthHostingIndexRoute: typeof BookingsauthHostingIndexRoute; - BookingsauthHostingGatheringIdDashboardScanRoute: typeof BookingsauthHostingGatheringIdDashboardScanRoute; - BookingsauthHostingGatheringIdDashboardIndexRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRoute; + BookingsauthAttendingIndexRoute: typeof BookingsauthAttendingIndexRoute + BookingsauthHostingIndexRoute: typeof BookingsauthHostingIndexRoute + BookingsauthHostingGatheringIdDashboardScanRoute: typeof BookingsauthHostingGatheringIdDashboardScanRoute + BookingsauthHostingGatheringIdDashboardIndexRoute: typeof BookingsauthHostingGatheringIdDashboardIndexRoute } const BookingsauthRouteRouteChildren: BookingsauthRouteRouteChildren = { - BookingsauthAttendingIndexRoute: BookingsauthAttendingIndexRoute, - BookingsauthHostingIndexRoute: BookingsauthHostingIndexRoute, - BookingsauthHostingGatheringIdDashboardScanRoute: - BookingsauthHostingGatheringIdDashboardScanRoute, - BookingsauthHostingGatheringIdDashboardIndexRoute: - BookingsauthHostingGatheringIdDashboardIndexRoute -}; + BookingsauthAttendingIndexRoute: BookingsauthAttendingIndexRoute, + BookingsauthHostingIndexRoute: BookingsauthHostingIndexRoute, + BookingsauthHostingGatheringIdDashboardScanRoute: + BookingsauthHostingGatheringIdDashboardScanRoute, + BookingsauthHostingGatheringIdDashboardIndexRoute: + BookingsauthHostingGatheringIdDashboardIndexRoute, +} -const BookingsauthRouteRouteWithChildren = BookingsauthRouteRoute._addFileChildren( - BookingsauthRouteRouteChildren -); +const BookingsauthRouteRouteWithChildren = + BookingsauthRouteRoute._addFileChildren(BookingsauthRouteRouteChildren) const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - BookingsauthRouteRoute: BookingsauthRouteRouteWithChildren, - GatheringsIndexRoute: GatheringsIndexRoute, - HealthcheckIndexRoute: HealthcheckIndexRoute, - LoginIndexRoute: LoginIndexRoute, - GatheringsauthCreateRoute: GatheringsauthCreateRoute, - GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, - GatheringsGatheringIdauthUpdateRoute: GatheringsGatheringIdauthUpdateRoute -}; + IndexRoute: IndexRoute, + BookingsauthRouteRoute: BookingsauthRouteRouteWithChildren, + GatheringsIndexRoute: GatheringsIndexRoute, + HealthcheckIndexRoute: HealthcheckIndexRoute, + LoginIndexRoute: LoginIndexRoute, + GatheringsauthCreateRoute: GatheringsauthCreateRoute, + GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, + GatheringsGatheringIdauthUpdateRoute: GatheringsGatheringIdauthUpdateRoute, +} export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes(); + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/src/evently.client/src/routes/gatherings/(auth).create.tsx b/src/evently.client/src/routes/gatherings/(auth).create.tsx index 4c99074..dae6d1a 100644 --- a/src/evently.client/src/routes/gatherings/(auth).create.tsx +++ b/src/evently.client/src/routes/gatherings/(auth).create.tsx @@ -1,9 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Category, Gathering } from "~/lib/domains/entities"; +import { Category, Gathering } from "~/domains/entities"; import { type JSX, useState } from "react"; import { authenticateRoute, createGathering, getCategories, sleep } from "~/lib/services"; import { type GatheringForm as IGatheringForm, useGatheringForm } from "./-services"; -import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; +import { GatheringReqDto, ToastContent } from "~/domains/models"; import { GatheringForm } from "~/routes/gatherings/-components"; export const Route = createFileRoute("/gatherings/(auth)/create")({ diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index f4b3278..bfeab0b 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -1,10 +1,10 @@ import { createFileRoute } from "@tanstack/react-router"; import { type JSX, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { Category, Gathering } from "~/lib/domains/entities"; +import { Category, Gathering } from "~/domains/entities"; import { getCategories, getGatherings, type GetGatheringsParams } from "~/lib/services"; import { Card } from "~/lib/components"; -import type { PageResult } from "~/lib/domains/interfaces"; +import type { PageResult } from "~/domains/interfaces"; import { FilterBar } from "~/routes/gatherings/-components"; import { Icon } from "@iconify/react/dist/offline"; diff --git a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs index c944538..39df43b 100644 --- a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs @@ -1,5 +1,4 @@ -using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Data; +using Evently.Server.Common.Data; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; diff --git a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs index fd0fa9a..f994f3e 100644 --- a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs @@ -1,5 +1,4 @@ -using Evently.Server.Common.Adapters.Data; -using Evently.Server.Common.Data; +using Evently.Server.Common.Data; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; From f7f38d51729e6d57870083d6f3a7f6340a6ff4fe Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:08:24 +0800 Subject: [PATCH 03/10] fix: fmt --- src/Evently.Server/.config/dotnet-tools.json | 7 + .../Common/Data/AppDbContext.cs | 783 ++++++++++----- .../Migrations/20250913035915_SQLServer.cs | 891 +++++++++++++----- .../20250927053802_UpdateSeededDates.cs | 582 ++++++++---- .../Common/Extensions/LoggerExtension.cs | 42 +- .../Common/Extensions/MapperExtension.cs | 37 +- .../Extensions/ServiceContainerExtensions.cs | 12 +- .../Common/Extensions/UtilsExtension.cs | 19 +- .../Middlewares/GlobalExceptionHandler.cs | 33 +- .../Domains/Entities/Account.cs | 12 +- .../Domains/Entities/Booking.cs | 25 +- .../Domains/Entities/Category.cs | 11 +- .../Domains/Entities/Gathering.cs | 17 +- .../Entities/GatheringCategoryDetail.cs | 9 +- .../ExternalLoginProviderException.cs | 4 +- .../Domains/Interfaces/IAccountsService.cs | 9 +- .../Domains/Interfaces/IBookingService.cs | 21 +- .../Domains/Interfaces/ICategoryService.cs | 5 +- .../Domains/Interfaces/IEmailerAdapter.cs | 5 +- .../Domains/Interfaces/IGatheringService.cs | 8 +- .../Domains/Interfaces/IMediaRenderer.cs | 18 +- .../Interfaces/IObjectStorageService.cs | 13 +- .../Domains/Models/AccountDto.cs | 2 +- .../Domains/Models/BookingReqDto.cs | 3 +- .../Models/GatheringCategoryDetailDto.cs | 2 +- .../Domains/Models/GatheringReqDto.cs | 2 +- .../Domains/Models/PageResult.cs | 5 +- src/Evently.Server/Domains/Models/Settings.cs | 30 +- src/Evently.Server/Evently.Server.csproj | 120 +-- .../Accounts/Controllers/AccountController.cs | 68 +- .../Services/AccountAuthorizationHandler.cs | 29 +- .../Accounts/Services/AccountExtensions.cs | 32 +- .../Accounts/Services/AccountService.cs | 64 +- .../Accounts/Services/AccountValidator.cs | 30 +- .../Controllers/BookingsController.cs | 70 +- .../Bookings/Services/BookingService.cs | 127 ++- .../Bookings/Services/BookingValidator.cs | 12 +- .../Controllers/CategoriesController.cs | 20 +- .../Categories/Services/CategoryService.cs | 30 +- .../Features/Emails/Services/EmailAdapter.cs | 36 +- .../Emails/Services/EmailBackgroundService.cs | 36 +- .../Features/Emails/Services/MediaRenderer.cs | 18 +- .../Files/Controllers/FilesController.cs | 47 +- .../Files/Services/ObjectStorageService.cs | 97 +- .../Controllers/GatheringsController.cs | 98 +- .../Gatherings/Services/GatheringService.cs | 91 +- .../Gatherings/Services/GatheringValidator.cs | 42 +- .../Controllers/HealthChecksController.cs | 20 +- src/Evently.Server/Program.cs | 121 ++- 49 files changed, 2539 insertions(+), 1276 deletions(-) diff --git a/src/Evently.Server/.config/dotnet-tools.json b/src/Evently.Server/.config/dotnet-tools.json index c69ef0a..be20a98 100644 --- a/src/Evently.Server/.config/dotnet-tools.json +++ b/src/Evently.Server/.config/dotnet-tools.json @@ -8,6 +8,13 @@ "jb" ], "rollForward": false + }, + "csharpier": { + "version": "1.2.1", + "commands": [ + "csharpier" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/src/Evently.Server/Common/Data/AppDbContext.cs b/src/Evently.Server/Common/Data/AppDbContext.cs index 8fae999..bc55bf6 100644 --- a/src/Evently.Server/Common/Data/AppDbContext.cs +++ b/src/Evently.Server/Common/Data/AppDbContext.cs @@ -1,314 +1,577 @@ +using System.Reflection; using Evently.Server.Domains.Entities; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System.Reflection; namespace Evently.Server.Common.Data; -public class AppDbContext(DbContextOptions options) : IdentityDbContext(options) { +public class AppDbContext(DbContextOptions options) + : IdentityDbContext(options) +{ public DbSet Accounts { get; set; } public DbSet Bookings { get; set; } public DbSet Gatherings { get; set; } public DbSet GatheringCategoryDetails { get; set; } public DbSet Categories { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { base.OnModelCreating(modelBuilder); // for unit testing, sqlite is used - if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") { + if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") + { ConfigureSqlite(modelBuilder); } // Postgres identity configuration - modelBuilder.Entity().Property(g => g.GatheringId) + modelBuilder + .Entity() + .Property(g => g.GatheringId) .HasIdentityOptions(startValue: 20); - modelBuilder.Entity().Property(c => c.CategoryId) + modelBuilder + .Entity() + .Property(c => c.CategoryId) .HasIdentityOptions(startValue: 20); SeedData(modelBuilder); } - private static void SeedData(ModelBuilder modelBuilder) { + private static void SeedData(ModelBuilder modelBuilder) + { // Fixed Account IDs for referencing const string hostUserId = "empty-user-12345"; const string guestUserId = "guest-user-22222"; // Seed Accounts (without proper password hashes - they won't be able to login) - modelBuilder.Entity().HasData( - new Account { - Id = hostUserId, // Fixed constant ID - UserName = "empty_user", - NormalizedUserName = "EMPTY_USER", - Email = "empty@example.com", - NormalizedEmail = "EMPTY@EXAMPLE.COM", - Name = "Empty User", - EmailConfirmed = false, - PasswordHash = null, // No password - unusable account - SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant - ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant - PhoneNumber = null, - PhoneNumberConfirmed = false, - TwoFactorEnabled = false, - LockoutEnabled = true, - AccessFailedCount = 0, - }, - new Account { - Id = guestUserId, // Fixed constant ID - UserName = "guest_user2", - NormalizedUserName = "GUEST_USER_2", - Email = "guest@example.com", - NormalizedEmail = "GUEST@EXAMPLE.COM", - Name = "Guest User", - EmailConfirmed = false, - PasswordHash = null, // No password - unusable account - SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant - ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant - PhoneNumber = null, - PhoneNumberConfirmed = false, - TwoFactorEnabled = false, - LockoutEnabled = true, - AccessFailedCount = 0, - } - ); + modelBuilder + .Entity() + .HasData( + new Account + { + Id = hostUserId, // Fixed constant ID + UserName = "empty_user", + NormalizedUserName = "EMPTY_USER", + Email = "empty@example.com", + NormalizedEmail = "EMPTY@EXAMPLE.COM", + Name = "Empty User", + EmailConfirmed = false, + PasswordHash = null, // No password - unusable account + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant + PhoneNumber = null, + PhoneNumberConfirmed = false, + TwoFactorEnabled = false, + LockoutEnabled = true, + AccessFailedCount = 0, + }, + new Account + { + Id = guestUserId, // Fixed constant ID + UserName = "guest_user2", + NormalizedUserName = "GUEST_USER_2", + Email = "guest@example.com", + NormalizedEmail = "GUEST@EXAMPLE.COM", + Name = "Guest User", + EmailConfirmed = false, + PasswordHash = null, // No password - unusable account + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant + PhoneNumber = null, + PhoneNumberConfirmed = false, + TwoFactorEnabled = false, + LockoutEnabled = true, + AccessFailedCount = 0, + } + ); // Seed Categories - modelBuilder.Entity().HasData( - new Category { CategoryId = 1, CategoryName = "Information Technology" }, - new Category { CategoryId = 2, CategoryName = "Business & Networking" }, - new Category { CategoryId = 3, CategoryName = "Arts & Culture" } - ); + modelBuilder + .Entity() + .HasData( + new Category { CategoryId = 1, CategoryName = "Information Technology" }, + new Category { CategoryId = 2, CategoryName = "Business & Networking" }, + new Category { CategoryId = 3, CategoryName = "Arts & Culture" } + ); // Singapore timezone offset TimeSpan singaporeOffset = TimeSpan.Zero; // Seed Gatherings - modelBuilder.Entity().HasData( - new Gathering { - GatheringId = 1, - Name = "Tech Innovation Summit", - Description = "A comprehensive summit exploring the latest in AI and machine learning", - Location = "Marina Bay Sands Convention Centre, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 9, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 5, hour: 17, minute: 0, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 2, - Name = "Startup Networking Night", - Description = "Connect with fellow entrepreneurs and investors", - Location = "Clarke Quay Central, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 18, minute: 30, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 10, hour: 22, minute: 0, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 3, - Name = "Digital Art Exhibition", - Description = "Showcasing contemporary digital art from emerging artists", - Location = "National Gallery Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 10, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 15, hour: 18, minute: 0, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 4, - Name = "Web Development Workshop", - Description = "Learn modern web development techniques and best practices", - Location = "Singapore Science Centre", - Start = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 13, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 8, hour: 17, minute: 0, second: 0, - singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 5, - Name = "Business Strategy Seminar", - Description = "Advanced strategies for scaling your business", - Location = "Raffles City Convention Centre, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 14, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 20, hour: 16, minute: 30, second: 0, - singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 6, - Name = "Photography Masterclass", - Description = "Professional photography techniques and portfolio building", - Location = "Gardens by the Bay, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 8, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 22, hour: 12, minute: 0, second: 0, - singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 7, - Name = "Mobile App Development Bootcamp", - Description = "Intensive bootcamp covering iOS and Android development", - Location = "NUS School of Computing, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 9, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 12, hour: 18, minute: 0, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 8, - Name = "Investment & Finance Forum", - Description = "Learn about personal finance and investment strategies", - Location = "Suntec Singapore Convention Centre", - Start = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 14, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 25, hour: 17, minute: 30, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 9, - Name = "Creative Writing Workshop", - Description = "Explore storytelling techniques and creative expression", - Location = "Esplanade Theatres, Singapore", - Start = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 10, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 28, hour: 15, minute: 0, second: 0, - singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 10, - Name = "Cloud Computing Conference", - Description = "Latest trends in cloud architecture and DevOps", - Location = "Singapore EXPO", - Start = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 9, minute: 30, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2026, month: 12, day: 30, hour: 17, minute: 30, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 11, - Name = "E-commerce Mastery", - Description = "Build and scale your online business effectively", - Location = "Marina Bay Financial Centre, Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 3, hour: 13, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 3, hour: 18, minute: 0, second: 0, singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 12, - Name = "Contemporary Dance Performance", - Description = "An evening of modern dance and artistic expression", - Location = "Victoria Theatre, Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 5, hour: 19, minute: 30, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 5, hour: 22, minute: 0, second: 0, singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 13, - Name = "Cybersecurity Awareness Training", - Description = "Essential cybersecurity practices for businesses", - Location = "Singapore Management University", - Start = new DateTimeOffset(year: 2027, month: 1, day: 8, hour: 10, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 8, hour: 16, minute: 0, second: 0, singaporeOffset), - OrganiserId = guestUserId, - }, - new Gathering { - GatheringId = 14, - Name = "Leadership Excellence Workshop", - Description = "Develop essential leadership skills for modern managers", - Location = "Orchard Hotel Singapore", - Start = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 9, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 10, hour: 17, minute: 0, second: 0, - singaporeOffset), - OrganiserId = hostUserId, - }, - new Gathering { - GatheringId = 15, - Name = "Film & Media Production Showcase", - Description = "Independent filmmakers present their latest works", - Location = "Singapore International Film Festival Venue", - Start = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 18, minute: 0, second: 0, - singaporeOffset), - End = new DateTimeOffset(year: 2027, month: 1, day: 12, hour: 23, minute: 0, second: 0, - singaporeOffset), - OrganiserId = guestUserId, - } - ); + modelBuilder + .Entity() + .HasData( + new Gathering + { + GatheringId = 1, + Name = "Tech Innovation Summit", + Description = + "A comprehensive summit exploring the latest in AI and machine learning", + Location = "Marina Bay Sands Convention Centre, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 5, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 5, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 2, + Name = "Startup Networking Night", + Description = "Connect with fellow entrepreneurs and investors", + Location = "Clarke Quay Central, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 10, + hour: 18, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 10, + hour: 22, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 3, + Name = "Digital Art Exhibition", + Description = "Showcasing contemporary digital art from emerging artists", + Location = "National Gallery Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 15, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 15, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 4, + Name = "Web Development Workshop", + Description = "Learn modern web development techniques and best practices", + Location = "Singapore Science Centre", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 8, + hour: 13, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 8, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 5, + Name = "Business Strategy Seminar", + Description = "Advanced strategies for scaling your business", + Location = "Raffles City Convention Centre, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 20, + hour: 14, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 20, + hour: 16, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 6, + Name = "Photography Masterclass", + Description = "Professional photography techniques and portfolio building", + Location = "Gardens by the Bay, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 22, + hour: 8, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 22, + hour: 12, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 7, + Name = "Mobile App Development Bootcamp", + Description = "Intensive bootcamp covering iOS and Android development", + Location = "NUS School of Computing, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 12, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 12, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 8, + Name = "Investment & Finance Forum", + Description = "Learn about personal finance and investment strategies", + Location = "Suntec Singapore Convention Centre", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 25, + hour: 14, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 25, + hour: 17, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 9, + Name = "Creative Writing Workshop", + Description = "Explore storytelling techniques and creative expression", + Location = "Esplanade Theatres, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 28, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 28, + hour: 15, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 10, + Name = "Cloud Computing Conference", + Description = "Latest trends in cloud architecture and DevOps", + Location = "Singapore EXPO", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 30, + hour: 9, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 30, + hour: 17, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 11, + Name = "E-commerce Mastery", + Description = "Build and scale your online business effectively", + Location = "Marina Bay Financial Centre, Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 3, + hour: 13, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 3, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 12, + Name = "Contemporary Dance Performance", + Description = "An evening of modern dance and artistic expression", + Location = "Victoria Theatre, Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 5, + hour: 19, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 5, + hour: 22, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 13, + Name = "Cybersecurity Awareness Training", + Description = "Essential cybersecurity practices for businesses", + Location = "Singapore Management University", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 8, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 8, + hour: 16, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 14, + Name = "Leadership Excellence Workshop", + Description = "Develop essential leadership skills for modern managers", + Location = "Orchard Hotel Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 10, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 10, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 15, + Name = "Film & Media Production Showcase", + Description = "Independent filmmakers present their latest works", + Location = "Singapore International Film Festival Venue", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 12, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 12, + hour: 23, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + } + ); // Seed GatheringCategoryDetails - modelBuilder.Entity().HasData( - new GatheringCategoryDetail { GatheringId = 1, CategoryId = 1 }, // Tech Innovation Summit -> IT - new GatheringCategoryDetail { GatheringId = 2, CategoryId = 2 }, // Startup Networking Night -> Business - new GatheringCategoryDetail { GatheringId = 3, CategoryId = 3 }, // Digital Art Exhibition -> Arts - new GatheringCategoryDetail { GatheringId = 4, CategoryId = 1 }, // Web Development Workshop -> IT - new GatheringCategoryDetail { GatheringId = 5, CategoryId = 2 }, // Business Strategy Seminar -> Business - new GatheringCategoryDetail { GatheringId = 6, CategoryId = 3 }, // Photography Masterclass -> Arts - new GatheringCategoryDetail { GatheringId = 7, CategoryId = 1 }, // Mobile App Development Bootcamp -> IT - new GatheringCategoryDetail { GatheringId = 8, CategoryId = 2 }, // Investment & Finance Forum -> Business - new GatheringCategoryDetail { GatheringId = 9, CategoryId = 3 }, // Creative Writing Workshop -> Arts - new GatheringCategoryDetail { GatheringId = 10, CategoryId = 1 }, // Cloud Computing Conference -> IT - new GatheringCategoryDetail { GatheringId = 11, CategoryId = 2 }, // E-commerce Mastery -> Business - new GatheringCategoryDetail { GatheringId = 12, CategoryId = 3 }, // Contemporary Dance Performance -> Arts - new GatheringCategoryDetail { GatheringId = 13, CategoryId = 1 }, // Cybersecurity Awareness Training -> IT - new GatheringCategoryDetail - { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business - new GatheringCategoryDetail { GatheringId = 15, CategoryId = 3 } // Film & Media Production Showcase -> Arts - ); + modelBuilder + .Entity() + .HasData( + new GatheringCategoryDetail { GatheringId = 1, CategoryId = 1 }, // Tech Innovation Summit -> IT + new GatheringCategoryDetail { GatheringId = 2, CategoryId = 2 }, // Startup Networking Night -> Business + new GatheringCategoryDetail { GatheringId = 3, CategoryId = 3 }, // Digital Art Exhibition -> Arts + new GatheringCategoryDetail { GatheringId = 4, CategoryId = 1 }, // Web Development Workshop -> IT + new GatheringCategoryDetail { GatheringId = 5, CategoryId = 2 }, // Business Strategy Seminar -> Business + new GatheringCategoryDetail { GatheringId = 6, CategoryId = 3 }, // Photography Masterclass -> Arts + new GatheringCategoryDetail { GatheringId = 7, CategoryId = 1 }, // Mobile App Development Bootcamp -> IT + new GatheringCategoryDetail { GatheringId = 8, CategoryId = 2 }, // Investment & Finance Forum -> Business + new GatheringCategoryDetail { GatheringId = 9, CategoryId = 3 }, // Creative Writing Workshop -> Arts + new GatheringCategoryDetail { GatheringId = 10, CategoryId = 1 }, // Cloud Computing Conference -> IT + new GatheringCategoryDetail { GatheringId = 11, CategoryId = 2 }, // E-commerce Mastery -> Business + new GatheringCategoryDetail { GatheringId = 12, CategoryId = 3 }, // Contemporary Dance Performance -> Arts + new GatheringCategoryDetail { GatheringId = 13, CategoryId = 1 }, // Cybersecurity Awareness Training -> IT + new GatheringCategoryDetail { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business + new GatheringCategoryDetail { GatheringId = 15, CategoryId = 3 } // Film & Media Production Showcase -> Arts + ); // Seed Bookings with fixed DateTimeOffset values // Fixed DateTimeOffset for seeding (static value) - DateTimeOffset fixedCreationTime = - new(year: 2025, month: 1, day: 1, hour: 0, minute: 0, second: 0, TimeSpan.Zero); - - modelBuilder.Entity().HasData( - new Booking { - BookingId = "book_abc123456", - GatheringId = 1, - AttendeeId = guestUserId, - CreationDateTime = fixedCreationTime, - CheckInDateTime = null, - CheckoutDateTime = null, - CancellationDateTime = null, - }, - new Booking { - BookingId = "book_def789012", - GatheringId = 2, - AttendeeId = hostUserId, - CreationDateTime = fixedCreationTime.AddHours(1), // Slightly different time - CheckInDateTime = null, - CheckoutDateTime = null, - CancellationDateTime = null, - } + DateTimeOffset fixedCreationTime = new( + year: 2025, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + TimeSpan.Zero ); + + modelBuilder + .Entity() + .HasData( + new Booking + { + BookingId = "book_abc123456", + GatheringId = 1, + AttendeeId = guestUserId, + CreationDateTime = fixedCreationTime, + CheckInDateTime = null, + CheckoutDateTime = null, + CancellationDateTime = null, + }, + new Booking + { + BookingId = "book_def789012", + GatheringId = 2, + AttendeeId = hostUserId, + CreationDateTime = fixedCreationTime.AddHours(1), // Slightly different time + CheckInDateTime = null, + CheckoutDateTime = null, + CancellationDateTime = null, + } + ); } // https://stackoverflow.com/a/76152994/6514532 - private static void ConfigureSqlite(ModelBuilder modelBuilder) { + private static void ConfigureSqlite(ModelBuilder modelBuilder) + { // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, // see the limitations here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations. // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754. - foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - IEnumerable properties = entityType.ClrType - .GetProperties() - .Where(p => p.PropertyType == typeof(DateTimeOffset) - || p.PropertyType == typeof(DateTimeOffset?)); - foreach (PropertyInfo property in properties) { + foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + { + IEnumerable properties = entityType + .ClrType.GetProperties() + .Where(p => + p.PropertyType == typeof(DateTimeOffset) + || p.PropertyType == typeof(DateTimeOffset?) + ); + foreach (PropertyInfo property in properties) + { modelBuilder .Entity(entityType.Name) .Property(property.Name) @@ -316,4 +579,4 @@ private static void ConfigureSqlite(ModelBuilder modelBuilder) { } } } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs index 1eab301..58f5dc2 100644 --- a/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs @@ -5,32 +5,67 @@ #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional -namespace Evently.Server.Common.Data.Migrations { +namespace Evently.Server.Common.Data.Migrations +{ /// - public partial class SQLServer : Migration { + public partial class SQLServer : Migration + { /// - protected override void Up(MigrationBuilder migrationBuilder) { + protected override void Up(MigrationBuilder migrationBuilder) + { migrationBuilder.CreateTable( name: "AspNetRoles", - columns: table => new { + columns: table => new + { Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + Name = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); + } + ); migrationBuilder.CreateTable( name: "AspNetUsers", - columns: table => new { + columns: table => new + { Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Name = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + UserName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedUserName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + Email = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedEmail = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), EmailConfirmed = table.Column(type: "bit", nullable: false), PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), @@ -38,357 +73,688 @@ protected override void Up(MigrationBuilder migrationBuilder) { PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnd = table.Column( + type: "datetimeoffset", + nullable: true + ), LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) + AccessFailedCount = table.Column(type: "int", nullable: false), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); + } + ); migrationBuilder.CreateTable( name: "Categories", - columns: table => new { - CategoryId = table.Column(type: "bigint", nullable: false) + columns: table => new + { + CategoryId = table + .Column(type: "bigint", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - CategoryName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Approved = table.Column(type: "bit", nullable: false) + CategoryName = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + Approved = table.Column(type: "bit", nullable: false), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_Categories", x => x.CategoryId); - }); + } + ); migrationBuilder.CreateTable( name: "Gatherings", - columns: table => new { - GatheringId = table.Column(type: "bigint", nullable: false) + columns: table => new + { + GatheringId = table + .Column(type: "bigint", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Description = table.Column(type: "nvarchar(max)", maxLength: 10000, nullable: false), + Name = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + Description = table.Column( + type: "nvarchar(max)", + maxLength: 10000, + nullable: false + ), Start = table.Column(type: "datetimeoffset", nullable: false), End = table.Column(type: "datetimeoffset", nullable: false), - Location = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CoverSrc = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - OrganiserId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) + Location = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + CoverSrc = table.Column( + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true + ), + OrganiserId = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + CancellationDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_Gatherings", x => x.GatheringId); - }); + } + ); migrationBuilder.CreateTable( name: "AspNetRoleClaims", - columns: table => new { - Id = table.Column(type: "int", nullable: false) + columns: table => new + { + Id = table + .Column(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), RoleId = table.Column(type: "nvarchar(450)", nullable: false), ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); table.ForeignKey( name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", column: x => x.RoleId, principalTable: "AspNetRoles", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "AspNetUserClaims", - columns: table => new { - Id = table.Column(type: "int", nullable: false) + columns: table => new + { + Id = table + .Column(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), UserId = table.Column(type: "nvarchar(450)", nullable: false), ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); table.ForeignKey( name: "FK_AspNetUserClaims_AspNetUsers_UserId", column: x => x.UserId, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "AspNetUserLogins", - columns: table => new { + columns: table => new + { LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) + ProviderDisplayName = table.Column( + type: "nvarchar(max)", + nullable: true + ), + UserId = table.Column(type: "nvarchar(450)", nullable: false), }, - constraints: table => { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + constraints: table => + { + table.PrimaryKey( + "PK_AspNetUserLogins", + x => new { x.LoginProvider, x.ProviderKey } + ); table.ForeignKey( name: "FK_AspNetUserLogins_AspNetUsers_UserId", column: x => x.UserId, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "AspNetUserRoles", - columns: table => new { + columns: table => new + { UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) + RoleId = table.Column(type: "nvarchar(450)", nullable: false), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); table.ForeignKey( name: "FK_AspNetUserRoles_AspNetRoles_RoleId", column: x => x.RoleId, principalTable: "AspNetRoles", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Cascade + ); table.ForeignKey( name: "FK_AspNetUserRoles_AspNetUsers_UserId", column: x => x.UserId, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "AspNetUserTokens", - columns: table => new { + columns: table => new + { UserId = table.Column(type: "nvarchar(450)", nullable: false), LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) + Value = table.Column(type: "nvarchar(max)", nullable: true), }, - constraints: table => { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + constraints: table => + { + table.PrimaryKey( + "PK_AspNetUserTokens", + x => new + { + x.UserId, + x.LoginProvider, + x.Name, + } + ); table.ForeignKey( name: "FK_AspNetUserTokens_AspNetUsers_UserId", column: x => x.UserId, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "Bookings", - columns: table => new { - BookingId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + columns: table => new + { + BookingId = table.Column( + type: "nvarchar(50)", + maxLength: 50, + nullable: false + ), AttendeeId = table.Column(type: "nvarchar(450)", nullable: false), GatheringId = table.Column(type: "bigint", nullable: false), - CreationDateTime = table.Column(type: "datetimeoffset", nullable: false), - CheckInDateTime = table.Column(type: "datetimeoffset", nullable: true), - CheckoutDateTime = table.Column(type: "datetimeoffset", nullable: true), - CancellationDateTime = table.Column(type: "datetimeoffset", nullable: true) + CreationDateTime = table.Column( + type: "datetimeoffset", + nullable: false + ), + CheckInDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + CheckoutDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + CancellationDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), }, - constraints: table => { + constraints: table => + { table.PrimaryKey("PK_Bookings", x => x.BookingId); table.ForeignKey( name: "FK_Bookings_AspNetUsers_AttendeeId", column: x => x.AttendeeId, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Cascade + ); table.ForeignKey( name: "FK_Bookings_Gatherings_GatheringId", column: x => x.GatheringId, principalTable: "Gatherings", principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateTable( name: "GatheringCategoryDetails", - columns: table => new { + columns: table => new + { GatheringId = table.Column(type: "bigint", nullable: false), - CategoryId = table.Column(type: "bigint", nullable: false) + CategoryId = table.Column(type: "bigint", nullable: false), }, - constraints: table => { - table.PrimaryKey("PK_GatheringCategoryDetails", x => new { x.GatheringId, x.CategoryId }); + constraints: table => + { + table.PrimaryKey( + "PK_GatheringCategoryDetails", + x => new { x.GatheringId, x.CategoryId } + ); table.ForeignKey( name: "FK_GatheringCategoryDetails_Categories_CategoryId", column: x => x.CategoryId, principalTable: "Categories", principalColumn: "CategoryId", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Cascade + ); table.ForeignKey( name: "FK_GatheringCategoryDetails_Gatherings_GatheringId", column: x => x.GatheringId, principalTable: "Gatherings", principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.InsertData( table: "AspNetUsers", - columns: new[] { - "Id", "AccessFailedCount", "ConcurrencyStamp", "Email", "EmailConfirmed", "LockoutEnabled", - "LockoutEnd", "Name", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", - "PhoneNumberConfirmed", "SecurityStamp", "TwoFactorEnabled", "UserName" + columns: new[] + { + "Id", + "AccessFailedCount", + "ConcurrencyStamp", + "Email", + "EmailConfirmed", + "LockoutEnabled", + "LockoutEnd", + "Name", + "NormalizedEmail", + "NormalizedUserName", + "PasswordHash", + "PhoneNumber", + "PhoneNumberConfirmed", + "SecurityStamp", + "TwoFactorEnabled", + "UserName", }, - values: new object[,] { + values: new object[,] + { + { + "empty-user-12345", + 0, + "EMPTY-CONCURRENCY-STAMP-12345", + "empty@example.com", + false, + true, + null, + "Empty User", + "EMPTY@EXAMPLE.COM", + "EMPTY_USER", + null, + null, + false, + "EMPTY-SECURITY-STAMP-12345", + false, + "empty_user", + }, { - "empty-user-12345", 0, "EMPTY-CONCURRENCY-STAMP-12345", "empty@example.com", false, true, null, - "Empty User", "EMPTY@EXAMPLE.COM", "EMPTY_USER", null, null, false, - "EMPTY-SECURITY-STAMP-12345", false, "empty_user" - }, { - "guest-user-22222", 0, "EMPTY-CONCURRENCY-STAMP-12345", "guest@example.com", false, true, null, - "Guest User", "GUEST@EXAMPLE.COM", "GUEST_USER_2", null, null, false, - "EMPTY-SECURITY-STAMP-12345", false, "guest_user2" - } - }); + "guest-user-22222", + 0, + "EMPTY-CONCURRENCY-STAMP-12345", + "guest@example.com", + false, + true, + null, + "Guest User", + "GUEST@EXAMPLE.COM", + "GUEST_USER_2", + null, + null, + false, + "EMPTY-SECURITY-STAMP-12345", + false, + "guest_user2", + }, + } + ); migrationBuilder.InsertData( table: "Categories", columns: new[] { "CategoryId", "Approved", "CategoryName" }, - values: new object[,] { + values: new object[,] + { { 1L, false, "Information Technology" }, { 2L, false, "Business & Networking" }, - { 3L, false, "Arts & Culture" } - }); + { 3L, false, "Arts & Culture" }, + } + ); migrationBuilder.InsertData( table: "Gatherings", - columns: new[] { - "GatheringId", "CancellationDateTime", "CoverSrc", "Description", "End", "Location", "Name", - "OrganiserId", "Start" + columns: new[] + { + "GatheringId", + "CancellationDateTime", + "CoverSrc", + "Description", + "End", + "Location", + "Name", + "OrganiserId", + "Start", }, - values: new object[,] { + values: new object[,] + { + { + 1L, + null, + "", + "A comprehensive summit exploring the latest in AI and machine learning", + new DateTimeOffset( + new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Marina Bay Sands Convention Centre, Singapore", + "Tech Innovation Summit", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 2L, + null, + "", + "Connect with fellow entrepreneurs and investors", + new DateTimeOffset( + new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Clarke Quay Central, Singapore", + "Startup Networking Night", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, { - 1L, null, "", "A comprehensive summit exploring the latest in AI and machine learning", - new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Marina Bay Sands Convention Centre, Singapore", "Tech Innovation Summit", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 2L, null, "", "Connect with fellow entrepreneurs and investors", - new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Clarke Quay Central, Singapore", "Startup Networking Night", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 3L, null, "", "Showcasing contemporary digital art from emerging artists", - new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "National Gallery Singapore", "Digital Art Exhibition", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 4L, null, "", "Learn modern web development techniques and best practices", - new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Singapore Science Centre", "Web Development Workshop", "guest-user-22222", - new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 5L, null, "", "Advanced strategies for scaling your business", - new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Raffles City Convention Centre, Singapore", "Business Strategy Seminar", "guest-user-22222", - new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 6L, null, "", "Professional photography techniques and portfolio building", - new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Gardens by the Bay, Singapore", "Photography Masterclass", "guest-user-22222", - new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 7L, null, "", "Intensive bootcamp covering iOS and Android development", - new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "NUS School of Computing, Singapore", "Mobile App Development Bootcamp", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 8L, null, "", "Learn about personal finance and investment strategies", - new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Suntec Singapore Convention Centre", "Investment & Finance Forum", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 9L, null, "", "Explore storytelling techniques and creative expression", - new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Esplanade Theatres, Singapore", "Creative Writing Workshop", "guest-user-22222", - new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 10L, null, "", "Latest trends in cloud architecture and DevOps", - new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Singapore EXPO", "Cloud Computing Conference", "empty-user-12345", - new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 11L, null, "", "Build and scale your online business effectively", - new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Marina Bay Financial Centre, Singapore", "E-commerce Mastery", "guest-user-22222", - new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 12L, null, "", "An evening of modern dance and artistic expression", - new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Victoria Theatre, Singapore", "Contemporary Dance Performance", "empty-user-12345", - new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 13L, null, "", "Essential cybersecurity practices for businesses", - new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Singapore Management University", "Cybersecurity Awareness Training", "guest-user-22222", - new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 14L, null, "", "Develop essential leadership skills for modern managers", - new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Orchard Hotel Singapore", "Leadership Excellence Workshop", "empty-user-12345", - new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }, { - 15L, null, "", "Independent filmmakers present their latest works", - new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - "Singapore International Film Festival Venue", "Film & Media Production Showcase", + 3L, + null, + "", + "Showcasing contemporary digital art from emerging artists", + new DateTimeOffset( + new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "National Gallery Singapore", + "Digital Art Exhibition", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 4L, + null, + "", + "Learn modern web development techniques and best practices", + new DateTimeOffset( + new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore Science Centre", + "Web Development Workshop", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 5L, + null, + "", + "Advanced strategies for scaling your business", + new DateTimeOffset( + new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Raffles City Convention Centre, Singapore", + "Business Strategy Seminar", "guest-user-22222", - new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - } - }); + new DateTimeOffset( + new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 6L, + null, + "", + "Professional photography techniques and portfolio building", + new DateTimeOffset( + new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Gardens by the Bay, Singapore", + "Photography Masterclass", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 7L, + null, + "", + "Intensive bootcamp covering iOS and Android development", + new DateTimeOffset( + new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "NUS School of Computing, Singapore", + "Mobile App Development Bootcamp", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 8L, + null, + "", + "Learn about personal finance and investment strategies", + new DateTimeOffset( + new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Suntec Singapore Convention Centre", + "Investment & Finance Forum", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 9L, + null, + "", + "Explore storytelling techniques and creative expression", + new DateTimeOffset( + new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Esplanade Theatres, Singapore", + "Creative Writing Workshop", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 10L, + null, + "", + "Latest trends in cloud architecture and DevOps", + new DateTimeOffset( + new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore EXPO", + "Cloud Computing Conference", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 11L, + null, + "", + "Build and scale your online business effectively", + new DateTimeOffset( + new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Marina Bay Financial Centre, Singapore", + "E-commerce Mastery", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 12L, + null, + "", + "An evening of modern dance and artistic expression", + new DateTimeOffset( + new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Victoria Theatre, Singapore", + "Contemporary Dance Performance", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 13L, + null, + "", + "Essential cybersecurity practices for businesses", + new DateTimeOffset( + new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore Management University", + "Cybersecurity Awareness Training", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 14L, + null, + "", + "Develop essential leadership skills for modern managers", + new DateTimeOffset( + new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Orchard Hotel Singapore", + "Leadership Excellence Workshop", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 15L, + null, + "", + "Independent filmmakers present their latest works", + new DateTimeOffset( + new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore International Film Festival Venue", + "Film & Media Production Showcase", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + } + ); migrationBuilder.InsertData( table: "Bookings", - columns: new[] { - "BookingId", "AttendeeId", "CancellationDateTime", "CheckInDateTime", "CheckoutDateTime", - "CreationDateTime", "GatheringId" + columns: new[] + { + "BookingId", + "AttendeeId", + "CancellationDateTime", + "CheckInDateTime", + "CheckoutDateTime", + "CreationDateTime", + "GatheringId", }, - values: new object[,] { + values: new object[,] + { + { + "book_abc123456", + "guest-user-22222", + null, + null, + null, + new DateTimeOffset( + new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + 1L, + }, { - "book_abc123456", "guest-user-22222", null, null, null, - new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - 1L - }, { - "book_def789012", "empty-user-12345", null, null, null, - new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - 2L - } - }); + "book_def789012", + "empty-user-12345", + null, + null, + null, + new DateTimeOffset( + new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + 2L, + }, + } + ); migrationBuilder.InsertData( table: "GatheringCategoryDetails", columns: new[] { "CategoryId", "GatheringId" }, - values: new object[,] { + values: new object[,] + { { 1L, 1L }, { 2L, 2L }, { 3L, 3L }, @@ -403,98 +769,99 @@ protected override void Up(MigrationBuilder migrationBuilder) { { 3L, 12L }, { 1L, 13L }, { 2L, 14L }, - { 3L, 15L } - }); + { 3L, 15L }, + } + ); migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", - column: "RoleId"); + column: "RoleId" + ); migrationBuilder.CreateIndex( name: "RoleNameIndex", table: "AspNetRoles", column: "NormalizedName", unique: true, - filter: "[NormalizedName] IS NOT NULL"); + filter: "[NormalizedName] IS NOT NULL" + ); migrationBuilder.CreateIndex( name: "IX_AspNetUserClaims_UserId", table: "AspNetUserClaims", - column: "UserId"); + column: "UserId" + ); migrationBuilder.CreateIndex( name: "IX_AspNetUserLogins_UserId", table: "AspNetUserLogins", - column: "UserId"); + column: "UserId" + ); migrationBuilder.CreateIndex( name: "IX_AspNetUserRoles_RoleId", table: "AspNetUserRoles", - column: "RoleId"); + column: "RoleId" + ); migrationBuilder.CreateIndex( name: "EmailIndex", table: "AspNetUsers", - column: "NormalizedEmail"); + column: "NormalizedEmail" + ); migrationBuilder.CreateIndex( name: "UserNameIndex", table: "AspNetUsers", column: "NormalizedUserName", unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); + filter: "[NormalizedUserName] IS NOT NULL" + ); migrationBuilder.CreateIndex( name: "IX_Bookings_AttendeeId", table: "Bookings", - column: "AttendeeId"); + column: "AttendeeId" + ); migrationBuilder.CreateIndex( name: "IX_Bookings_GatheringId", table: "Bookings", - column: "GatheringId"); + column: "GatheringId" + ); migrationBuilder.CreateIndex( name: "IX_GatheringCategoryDetails_CategoryId", table: "GatheringCategoryDetails", - column: "CategoryId"); + column: "CategoryId" + ); } /// - protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "AspNetRoleClaims"); - migrationBuilder.DropTable( - name: "AspNetUserClaims"); + migrationBuilder.DropTable(name: "AspNetUserClaims"); - migrationBuilder.DropTable( - name: "AspNetUserLogins"); + migrationBuilder.DropTable(name: "AspNetUserLogins"); - migrationBuilder.DropTable( - name: "AspNetUserRoles"); + migrationBuilder.DropTable(name: "AspNetUserRoles"); - migrationBuilder.DropTable( - name: "AspNetUserTokens"); + migrationBuilder.DropTable(name: "AspNetUserTokens"); - migrationBuilder.DropTable( - name: "Bookings"); + migrationBuilder.DropTable(name: "Bookings"); - migrationBuilder.DropTable( - name: "GatheringCategoryDetails"); + migrationBuilder.DropTable(name: "GatheringCategoryDetails"); - migrationBuilder.DropTable( - name: "AspNetRoles"); + migrationBuilder.DropTable(name: "AspNetRoles"); - migrationBuilder.DropTable( - name: "AspNetUsers"); + migrationBuilder.DropTable(name: "AspNetUsers"); - migrationBuilder.DropTable( - name: "Categories"); + migrationBuilder.DropTable(name: "Categories"); - migrationBuilder.DropTable( - name: "Gatherings"); + migrationBuilder.DropTable(name: "Gatherings"); } } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs index 3cc32c9..e997e59 100644 --- a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs @@ -3,405 +3,601 @@ #nullable disable -namespace Evently.Server.Common.Data.Migrations { +namespace Evently.Server.Common.Data.Migrations +{ /// - public partial class UpdateSeededDates : Migration { + public partial class UpdateSeededDates : Migration + { /// - protected override void Up(MigrationBuilder migrationBuilder) { + protected override void Up(MigrationBuilder migrationBuilder) + { migrationBuilder.UpdateData( table: "Bookings", keyColumn: "BookingId", keyValue: "book_abc123456", column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset( + new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); migrationBuilder.UpdateData( table: "Bookings", keyColumn: "BookingId", keyValue: "book_def789012", column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset( + new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 1L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 2L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 3L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 4L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 5L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 6L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 7L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 8L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 9L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 10L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 11L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 12L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 13L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 14L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 15L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); } /// - protected override void Down(MigrationBuilder migrationBuilder) { + protected override void Down(MigrationBuilder migrationBuilder) + { migrationBuilder.UpdateData( table: "Bookings", keyColumn: "BookingId", keyValue: "book_abc123456", column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset( + new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); migrationBuilder.UpdateData( table: "Bookings", keyColumn: "BookingId", keyValue: "book_def789012", column: "CreationDateTime", - value: new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset( + new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 1L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 2L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 3L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 4L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 5L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 6L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 7L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 8L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 9L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 10L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 11L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 12L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 13L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 14L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); migrationBuilder.UpdateData( table: "Gatherings", keyColumn: "GatheringId", keyValue: 15L, columns: new[] { "End", "Start" }, - values: new object[] { - new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)), - new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0)) - }); + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); } } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Extensions/LoggerExtension.cs b/src/Evently.Server/Common/Extensions/LoggerExtension.cs index eb99e5f..a9c6f7d 100644 --- a/src/Evently.Server/Common/Extensions/LoggerExtension.cs +++ b/src/Evently.Server/Common/Extensions/LoggerExtension.cs @@ -1,39 +1,45 @@ namespace Evently.Server.Common.Extensions; -public static partial class LoggerExtension { - [LoggerMessage( - EventId = 1, - Level = LogLevel.Information, - Message = "{key}: {value}")] - public static partial void LogValue( - this ILogger logger, string key, string? value); +public static partial class LoggerExtension +{ + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "{key}: {value}")] + public static partial void LogValue(this ILogger logger, string key, string? value); [LoggerMessage( EventId = 2, Level = LogLevel.Information, - Message = "Callback Url: {callbackUrl}")] - public static partial void LogCallbackUrl( - this ILogger logger, string? callbackUrl); + Message = "Callback Url: {callbackUrl}" + )] + public static partial void LogCallbackUrl(this ILogger logger, string? callbackUrl); [LoggerMessage( EventId = 3, Level = LogLevel.Information, - Message = "Email sent successfully to {email}")] - public static partial void LogSuccessEmail( - this ILogger logger, string? email); + Message = "Email sent successfully to {email}" + )] + public static partial void LogSuccessEmail(this ILogger logger, string? email); [LoggerMessage( EventId = 4, Level = LogLevel.Error, - Message = "Error occurred at {context}: {errorMsg}")] + Message = "Error occurred at {context}: {errorMsg}" + )] public static partial void LogErrorContext( - this ILogger logger, string context, string errorMsg); + this ILogger logger, + string context, + string errorMsg + ); [LoggerMessage( EventId = 5, Level = LogLevel.Error, - Message = "Analyze image failed. Status code: {statusCode}, Error code: {errorCode}, Error message: {errMsg}")] + Message = "Analyze image failed. Status code: {statusCode}, Error code: {errorCode}, Error message: {errMsg}" + )] public static partial void LogContentModerationError( - this ILogger logger, string statusCode, string errorCode, string errMsg); + this ILogger logger, + string statusCode, + string errorCode, + string errMsg + ); // -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Extensions/MapperExtension.cs b/src/Evently.Server/Common/Extensions/MapperExtension.cs index a4dc87b..10f9c13 100644 --- a/src/Evently.Server/Common/Extensions/MapperExtension.cs +++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs @@ -3,15 +3,22 @@ namespace Evently.Server.Common.Extensions; -public static class MapperExtension { - public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) { - List gatheringCategoryDetails = gatheringReqDto.GatheringCategoryDetails - .Select((detail) => new GatheringCategoryDetail { - GatheringId = gatheringReqDto.GatheringId, - CategoryId = detail.CategoryId, - }) +public static class MapperExtension +{ + public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) + { + List gatheringCategoryDetails = gatheringReqDto + .GatheringCategoryDetails.Select( + (detail) => + new GatheringCategoryDetail + { + GatheringId = gatheringReqDto.GatheringId, + CategoryId = detail.CategoryId, + } + ) .ToList(); - Gathering gathering = new() { + Gathering gathering = new() + { GatheringId = gatheringReqDto.GatheringId, Name = gatheringReqDto.Name, Description = gatheringReqDto.Description, @@ -26,8 +33,10 @@ public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) { return gathering; } - public static Booking ToBooking(this BookingReqDto bookingReqDto) { - return new Booking { + public static Booking ToBooking(this BookingReqDto bookingReqDto) + { + return new Booking + { BookingId = bookingReqDto.BookingId, AttendeeId = bookingReqDto.AttendeeId, GatheringId = bookingReqDto.GatheringId, @@ -38,7 +47,8 @@ public static Booking ToBooking(this BookingReqDto bookingReqDto) { }; } - public static BookingReqDto ToBookingDto(this Booking booking) { + public static BookingReqDto ToBookingDto(this Booking booking) + { return new BookingReqDto( booking.BookingId, booking.AttendeeId, @@ -50,7 +60,8 @@ public static BookingReqDto ToBookingDto(this Booking booking) { ); } - public static AccountDto ToAccountDto(this Account account) { + public static AccountDto ToAccountDto(this Account account) + { return new AccountDto( account.Id, Email: account.Email ?? string.Empty, @@ -58,4 +69,4 @@ public static AccountDto ToAccountDto(this Account account) { account.Name ); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs index 64440a8..71c2eb8 100644 --- a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs +++ b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs @@ -3,9 +3,13 @@ namespace Evently.Server.Common.Extensions; -public static class ServiceContainerExtensions { - public static IOptions LoadAppConfiguration(this IServiceCollection services, - ConfigurationManager configuration) { +public static class ServiceContainerExtensions +{ + public static IOptions LoadAppConfiguration( + this IServiceCollection services, + ConfigurationManager configuration + ) + { // load .env variables, in addition to appsettings.json that is loaded by default configuration.AddEnvironmentVariables(); @@ -19,4 +23,4 @@ public static IOptions LoadAppConfiguration(this IServiceCollection se IOptions options = Options.Create(settings); return options; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Extensions/UtilsExtension.cs b/src/Evently.Server/Common/Extensions/UtilsExtension.cs index 135b954..c90b9bd 100644 --- a/src/Evently.Server/Common/Extensions/UtilsExtension.cs +++ b/src/Evently.Server/Common/Extensions/UtilsExtension.cs @@ -1,22 +1,23 @@ namespace Evently.Server.Common.Extensions; -public static class UtilsExtension { - public static Uri RootUri(this HttpRequest request) { - UriBuilder uriBuilder = new() { - Scheme = request.Scheme, - Host = request.Host.Host, - }; - if (request.Host.Port.HasValue) { +public static class UtilsExtension +{ + public static Uri RootUri(this HttpRequest request) + { + UriBuilder uriBuilder = new() { Scheme = request.Scheme, Host = request.Host.Host }; + if (request.Host.Port.HasValue) + { uriBuilder.Port = request.Host.Port.Value; } return uriBuilder.Uri; } - public static async Task ToBinaryData(this IFormFile file) { + public static async Task ToBinaryData(this IFormFile file) + { using MemoryStream ms = new(); await file.CopyToAsync(ms); byte[] bytes = ms.ToArray(); return BinaryData.FromBytes(bytes); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs index 327df06..b2be2c8 100644 --- a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs +++ b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs @@ -2,36 +2,49 @@ namespace Evently.Server.Common.Middlewares; -public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler { - public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, - CancellationToken cancellationToken) { +public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler +{ + public async ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken + ) + { string exceptionMessage = exception.Message; logger.LogError( "Error Message: {exceptionMessage}, Time of occurrence {time}", exceptionMessage, - DateTime.UtcNow); + DateTime.UtcNow + ); - switch (exception) { + switch (exception) + { case ArgumentException: httpContext.Response.StatusCode = 400; - await httpContext.Response.WriteAsJsonAsync(value: new { + await httpContext.Response.WriteAsJsonAsync( + value: new + { Title = "Validation Error", Detail = exceptionMessage, Status = StatusCodes.Status400BadRequest, }, - cancellationToken); + cancellationToken + ); break; default: httpContext.Response.StatusCode = 500; - await httpContext.Response.WriteAsJsonAsync(value: new { + await httpContext.Response.WriteAsJsonAsync( + value: new + { Title = "Server Error", Detail = "An unexpected error occurred.", Status = StatusCodes.Status500InternalServerError, }, - cancellationToken); + cancellationToken + ); break; } return true; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Entities/Account.cs b/src/Evently.Server/Domains/Entities/Account.cs index 3c13971..fcf8268 100644 --- a/src/Evently.Server/Domains/Entities/Account.cs +++ b/src/Evently.Server/Domains/Entities/Account.cs @@ -1,13 +1,15 @@ -using Microsoft.AspNetCore.Identity; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Identity; namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] -public class Account : IdentityUser { - [StringLength(100)] public string Name { get; set; } = string.Empty; +public class Account : IdentityUser +{ + [StringLength(100)] + public string Name { get; set; } = string.Empty; public List Bookings { get; set; } = []; -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Entities/Booking.cs b/src/Evently.Server/Domains/Entities/Booking.cs index c8cce03..088cca0 100644 --- a/src/Evently.Server/Domains/Entities/Booking.cs +++ b/src/Evently.Server/Domains/Entities/Booking.cs @@ -1,28 +1,35 @@ -using Evently.Server.Common.Extensions; -using Evently.Server.Domains.Models; -using NanoidDotNet; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; +using Evently.Server.Common.Extensions; +using Evently.Server.Domains.Models; +using NanoidDotNet; namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] -public class Booking { +public class Booking +{ [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [StringLength(50)] public string BookingId { get; set; } = $"book_{Nanoid.Generate(size: 10)}"; [ForeignKey("Account")] - [SuppressMessage("ReSharper", "EntityFramework.ModelValidation.UnlimitedStringLength", - Justification = "MSSQL does not allow specified string limit")] + [SuppressMessage( + "ReSharper", + "EntityFramework.ModelValidation.UnlimitedStringLength", + Justification = "MSSQL does not allow specified string limit" + )] public string AttendeeId { get; set; } = string.Empty; - [JsonIgnore] public Account? Account { get; set; } - [NotMapped] public AccountDto? AccountDto => Account?.ToAccountDto(); + [JsonIgnore] + public Account? Account { get; set; } + + [NotMapped] + public AccountDto? AccountDto => Account?.ToAccountDto(); public long GatheringId { get; set; } public Gathering? Gathering { get; set; } @@ -31,4 +38,4 @@ public class Booking { public DateTimeOffset? CheckInDateTime { get; set; } public DateTimeOffset? CheckoutDateTime { get; set; } public DateTimeOffset? CancellationDateTime { get; set; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Entities/Category.cs b/src/Evently.Server/Domains/Entities/Category.cs index 5ad6a1b..bd36b2e 100644 --- a/src/Evently.Server/Domains/Entities/Category.cs +++ b/src/Evently.Server/Domains/Entities/Category.cs @@ -5,11 +5,14 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] -public class Category { - [Key] public long CategoryId { get; set; } +public class Category +{ + [Key] + public long CategoryId { get; set; } - [StringLength(100)] public string CategoryName { get; set; } = string.Empty; + [StringLength(100)] + public string CategoryName { get; set; } = string.Empty; public bool Approved { get; set; } public List GatheringCategoryDetails { get; set; } = []; -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Entities/Gathering.cs b/src/Evently.Server/Domains/Entities/Gathering.cs index 74a410b..49a7f2c 100644 --- a/src/Evently.Server/Domains/Entities/Gathering.cs +++ b/src/Evently.Server/Domains/Entities/Gathering.cs @@ -6,21 +6,26 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] -public class Gathering { +public class Gathering +{ [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long GatheringId { get; set; } - [StringLength(100)] public string Name { get; set; } = string.Empty; + [StringLength(100)] + public string Name { get; set; } = string.Empty; - [StringLength(10_000)] public string Description { get; set; } = string.Empty; + [StringLength(10_000)] + public string Description { get; set; } = string.Empty; public DateTimeOffset Start { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset End { get; set; } = DateTimeOffset.UtcNow; - [StringLength(100)] public string Location { get; set; } = string.Empty; + [StringLength(100)] + public string Location { get; set; } = string.Empty; - [StringLength(1000)] public string? CoverSrc { get; set; } = string.Empty; + [StringLength(1000)] + public string? CoverSrc { get; set; } = string.Empty; // convenience field that acts as a readonly field for Account that created the Gathering [ForeignKey("Account")] @@ -31,4 +36,4 @@ public class Gathering { public List Bookings { get; set; } = []; public List GatheringCategoryDetails { get; set; } = []; -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs index b6b8f1d..660684d 100644 --- a/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs +++ b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs @@ -1,14 +1,15 @@ -using Microsoft.EntityFrameworkCore; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore; namespace Evently.Server.Domains.Entities; [PrimaryKey(propertyName: nameof(GatheringId), nameof(CategoryId))] [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] -public class GatheringCategoryDetail { +public class GatheringCategoryDetail +{ public long GatheringId { get; set; } public Gathering? Gathering { get; set; } public long CategoryId { get; set; } public Category? Category { get; set; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs index 227e6d1..462a1d6 100644 --- a/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs +++ b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs @@ -1,4 +1,4 @@ namespace Evently.Server.Domains.Exceptions; -public class ExternalLoginProviderException(string provider, string message) : - Exception($"External login provider: {provider} error occurred: {message}"); \ No newline at end of file +public class ExternalLoginProviderException(string provider, string message) + : Exception($"External login provider: {provider} error occurred: {message}"); diff --git a/src/Evently.Server/Domains/Interfaces/IAccountsService.cs b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs index e5e7637..8318c4e 100644 --- a/src/Evently.Server/Domains/Interfaces/IAccountsService.cs +++ b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs @@ -1,9 +1,10 @@ -using Evently.Server.Domains.Entities; -using System.Security.Claims; +using System.Security.Claims; +using Evently.Server.Domains.Entities; namespace Evently.Server.Domains.Interfaces; -public interface IAccountsService { +public interface IAccountsService +{ Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider); Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/IBookingService.cs b/src/Evently.Server/Domains/Interfaces/IBookingService.cs index 7a35638..d5db972 100644 --- a/src/Evently.Server/Domains/Interfaces/IBookingService.cs +++ b/src/Evently.Server/Domains/Interfaces/IBookingService.cs @@ -3,16 +3,25 @@ namespace Evently.Server.Domains.Interfaces; -public interface IBookingService { +public interface IBookingService +{ Task GetBooking(string bookingId); - Task> GetBookings(string? accountId, long? gatheringId, - DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + Task> GetBookings( + string? accountId, + long? gatheringId, + DateTimeOffset? checkInStart, + DateTimeOffset? checkInEnd, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, - bool? isCancelled, int? offset, int? limit); + bool? isCancelled, + int? offset, + int? limit + ); Task CreateBooking(BookingReqDto bookingReqDto); Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto); Task RenderTicket(string bookingId); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/ICategoryService.cs b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs index d08f46e..4404146 100644 --- a/src/Evently.Server/Domains/Interfaces/ICategoryService.cs +++ b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs @@ -3,7 +3,8 @@ namespace Evently.Server.Domains.Interfaces; -public interface ICategoryService { +public interface ICategoryService +{ Task> GetCategories(long? gatheringId, bool? approved); Task CreateCategory(Category category); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs index a996a49..9422c30 100644 --- a/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs +++ b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs @@ -1,6 +1,7 @@ namespace Evently.Server.Domains.Interfaces; -public interface IEmailerAdapter { +public interface IEmailerAdapter +{ // senderEmail: Either actual Sender email or email of the third party that IEmailer sends on behalf of. Task SendEmailAsync(string senderEmail, string recipientEmail, string subject, string body); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/IGatheringService.cs b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs index 2a303ee..e176e71 100644 --- a/src/Evently.Server/Domains/Interfaces/IGatheringService.cs +++ b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs @@ -3,7 +3,8 @@ namespace Evently.Server.Domains.Interfaces; -public interface IGatheringService { +public interface IGatheringService +{ Task GetGathering(long gatheringId); Task> GetGatherings( @@ -17,9 +18,10 @@ Task> GetGatherings( bool? isCancelled, HashSet? categoryIds, int? offset, - int? limit); + int? limit + ); Task CreateGathering(GatheringReqDto gatheringReqDto); Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto); Task DeleteGathering(long gatheringId); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs index 072c7fe..03fa53f 100644 --- a/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs +++ b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs @@ -1,14 +1,18 @@ -using Microsoft.AspNetCore.Components; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; namespace Evently.Server.Domains.Interfaces; -public interface IMediaRenderer { - Task RenderComponentHtml(Dictionary dictionary) where T : IComponent; +public interface IMediaRenderer +{ + Task RenderComponentHtml(Dictionary dictionary) + where T : IComponent; BinaryData RenderQr(string qrData); - [SuppressMessage("ReSharper", + [SuppressMessage( + "ReSharper", "UnusedMember.Global", - Justification = "May need to convert ticket HTML to PDF in future")] + Justification = "May need to convert ticket HTML to PDF in future" + )] BinaryData RenderPdf(string html); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs index 65d810b..a4e3707 100644 --- a/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs +++ b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs @@ -3,14 +3,19 @@ namespace Evently.Server.Domains.Interfaces; [SuppressMessage("ReSharper", "UnusedMember.Global")] -public interface IObjectStorageService { +public interface IObjectStorageService +{ // mimeType determines the browser's behaviour when the file is accessed directly via the browser, // e.g. download ("application/octet-stream") or view ("images/*)" the file. - Task UploadFile(string containerName, string fileName, BinaryData binaryData, - string mimeType = "application/octet-stream"); + Task UploadFile( + string containerName, + string fileName, + BinaryData binaryData, + string mimeType = "application/octet-stream" + ); Task GetFileUri(string containerName, string fileName); Task GetFile(string containerName, string fileName); Task IsFileExists(string containerName, string fileName); Task PassesContentModeration(BinaryData binaryData); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Models/AccountDto.cs b/src/Evently.Server/Domains/Models/AccountDto.cs index 96caeba..e251d3f 100644 --- a/src/Evently.Server/Domains/Models/AccountDto.cs +++ b/src/Evently.Server/Domains/Models/AccountDto.cs @@ -3,4 +3,4 @@ namespace Evently.Server.Domains.Models; [UsedImplicitly] -public sealed record AccountDto(string Id, string Email, string Username, string Name); \ No newline at end of file +public sealed record AccountDto(string Id, string Email, string Username, string Name); diff --git a/src/Evently.Server/Domains/Models/BookingReqDto.cs b/src/Evently.Server/Domains/Models/BookingReqDto.cs index 6b631d2..c750ca6 100644 --- a/src/Evently.Server/Domains/Models/BookingReqDto.cs +++ b/src/Evently.Server/Domains/Models/BookingReqDto.cs @@ -7,4 +7,5 @@ public sealed record BookingReqDto( DateTimeOffset CreationDateTime, DateTimeOffset? CheckInDateTime, DateTimeOffset? CheckoutDateTime, - DateTimeOffset? CancellationDateTime); \ No newline at end of file + DateTimeOffset? CancellationDateTime +); diff --git a/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs b/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs index 4604018..ba0ec6d 100644 --- a/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs +++ b/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs @@ -3,4 +3,4 @@ namespace Evently.Server.Domains.Models; [UsedImplicitly] -public sealed record GatheringCategoryDetailDto(long GatheringId, long CategoryId); \ No newline at end of file +public sealed record GatheringCategoryDetailDto(long GatheringId, long CategoryId); diff --git a/src/Evently.Server/Domains/Models/GatheringReqDto.cs b/src/Evently.Server/Domains/Models/GatheringReqDto.cs index c6e8121..5c70422 100644 --- a/src/Evently.Server/Domains/Models/GatheringReqDto.cs +++ b/src/Evently.Server/Domains/Models/GatheringReqDto.cs @@ -11,4 +11,4 @@ public sealed record GatheringReqDto( string OrganiserId, string? CoverSrc, List GatheringCategoryDetails -); \ No newline at end of file +); diff --git a/src/Evently.Server/Domains/Models/PageResult.cs b/src/Evently.Server/Domains/Models/PageResult.cs index c15fcca..0117a71 100644 --- a/src/Evently.Server/Domains/Models/PageResult.cs +++ b/src/Evently.Server/Domains/Models/PageResult.cs @@ -1,6 +1,7 @@ namespace Evently.Server.Domains.Models; -public sealed class PageResult { +public sealed class PageResult +{ public List Items { get; init; } = []; public int TotalCount { get; init; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Domains/Models/Settings.cs b/src/Evently.Server/Domains/Models/Settings.cs index 30fa938..7fdb226 100644 --- a/src/Evently.Server/Domains/Models/Settings.cs +++ b/src/Evently.Server/Domains/Models/Settings.cs @@ -3,41 +3,51 @@ namespace Evently.Server.Domains.Models; -public sealed class Settings { +public sealed class Settings +{ public StorageAccount StorageAccount { get; init; } = new(); - [NotMapped] public AuthSetting Authentication { get; init; } = new(); + [NotMapped] + public AuthSetting Authentication { get; init; } = new(); - [NotMapped] public EmailSettings EmailSettings { get; init; } = new(); - [NotMapped] public AzureAIFoundry AzureAiFoundry { get; init; } = new(); + [NotMapped] + public EmailSettings EmailSettings { get; init; } = new(); + + [NotMapped] + public AzureAIFoundry AzureAiFoundry { get; init; } = new(); } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] -public sealed class StorageAccount { +public sealed class StorageAccount +{ public string AccountName { get; init; } = string.Empty; public string AzureStorageConnectionString { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] -public sealed class AuthSetting { +public sealed class AuthSetting +{ public OAuthSetting Google { get; init; } = new(); } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] -public sealed class OAuthSetting { +public sealed class OAuthSetting +{ public string ClientId { get; init; } = string.Empty; public string ClientSecret { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] -public sealed class EmailSettings { +public sealed class EmailSettings +{ public string ActualFrom { get; init; } = string.Empty; public string SmtpPassword { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] // ReSharper disable once InconsistentNaming -public sealed class AzureAIFoundry { +public sealed class AzureAIFoundry +{ public string ContentSafetyKey { get; init; } = string.Empty; public string ContentSafetyEndpoint { get; init; } = string.Empty; -} \ No newline at end of file +} diff --git a/src/Evently.Server/Evently.Server.csproj b/src/Evently.Server/Evently.Server.csproj index c1bfee0..d5a4f6f 100644 --- a/src/Evently.Server/Evently.Server.csproj +++ b/src/Evently.Server/Evently.Server.csproj @@ -1,61 +1,69 @@ + + net9.0 + enable + enable + ee94e8bf-92f6-4422-b368-12a6d0a8704d + Linux + ..\evently.client + npm run dev + https://localhost:50071 + - - net9.0 - enable - enable - ee94e8bf-92f6-4422-b368-12a6d0a8704d - Linux - ..\evently.client - npm run dev - https://localhost:50071 - + + + + + + + + + + + + + + + 9.*-* + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + - - - - - - - - - - - - - - - 9.*-* - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - + + + false + + - - - false - - + + <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\BlazorApp.razor" /> + <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\Routes.razor" /> + - - <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\BlazorApp.razor" /> - <_ContentIncludedByDefault Remove="Common\Adapters\Blazor\Routes.razor" /> - - - - - - - \ No newline at end of file + + + + + diff --git a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs index a90fbf8..dcb2207 100644 --- a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs +++ b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs @@ -1,4 +1,5 @@ -using Evently.Server.Common.Extensions; +using System.Security.Claims; +using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Exceptions; using Evently.Server.Domains.Interfaces; @@ -7,7 +8,6 @@ using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; namespace Evently.Server.Features.Accounts.Controllers; @@ -16,8 +16,11 @@ namespace Evently.Server.Features.Accounts.Controllers; [Route("api/v1/Auth/external")] public sealed class AccountController( IAccountsService accountService, - ILogger logger) : ControllerBase { - private readonly Dictionary _authSchemes = new() { + ILogger logger +) : ControllerBase +{ + private readonly Dictionary _authSchemes = new() + { { "google", GoogleDefaults.AuthenticationScheme }, { "microsoft", MicrosoftAccountDefaults.AuthenticationScheme }, }; @@ -34,16 +37,15 @@ public sealed class AccountController( * 8. Callback() method redirects to FE specified callback URL. */ [HttpGet("{provider}/login")] - public IActionResult Login(string provider, string? originUrl = "") { + public IActionResult Login(string provider, string? originUrl = "") + { Uri rootUri = Request.RootUri(); string uri = Url.Action("Callback", "Account", values: new { provider }) ?? ""; - UriBuilder combined = new(rootUri) { - Path = uri, - Query = $"originUrl={originUrl}", - }; + UriBuilder combined = new(rootUri) { Path = uri, Query = $"originUrl={originUrl}" }; logger.LogCallbackUrl(combined.Uri.AbsoluteUri); - AuthenticationProperties properties = new() { + AuthenticationProperties properties = new() + { RedirectUri = combined.Uri.AbsoluteUri, IsPersistent = true, }; @@ -51,33 +53,48 @@ public IActionResult Login(string provider, string? originUrl = "") { } [HttpGet("{provider}/callback")] - public async Task> Callback(string provider, [FromQuery] string originUrl = "") { - AuthenticateResult result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); + public async Task> Callback( + string provider, + [FromQuery] string originUrl = "" + ) + { + AuthenticateResult result = await HttpContext.AuthenticateAsync( + GoogleDefaults.AuthenticationScheme + ); - if (!result.Succeeded || result.Principal is null) { + if (!result.Succeeded || result.Principal is null) + { return Unauthorized(); } ClaimsPrincipal claimsPrincipal = result.Principal; - if (claimsPrincipal == null) { + if (claimsPrincipal == null) + { throw new ExternalLoginProviderException(provider, "ClaimsPrincipal is null"); } - await accountService.ExternalLogin(claimsPrincipal, - loginProvider: _authSchemes.GetValueOrDefault(provider) ?? ""); + await accountService.ExternalLogin( + claimsPrincipal, + loginProvider: _authSchemes.GetValueOrDefault(provider) ?? "" + ); return Redirect(originUrl); } [HttpGet("account", Name = "GetAccount from Cookie")] - public async Task GetAccount() { - AuthenticateResult result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); - if (!result.Succeeded) { + public async Task GetAccount() + { + AuthenticateResult result = await HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!result.Succeeded) + { return Unauthorized(); } ClaimsPrincipal principal = result.Principal ?? new ClaimsPrincipal(); Account? user = await accountService.FindByClaimsPrincipalAsync(principal); - if (user is null) { + if (user is null) + { return NotFound(new { message = "User not found" }); } @@ -85,19 +102,22 @@ public async Task GetAccount() { } [HttpPost("logout")] - public async Task Logout(string? redirectUrl = "") { + public async Task Logout(string? redirectUrl = "") + { // Sign out of an external identity provider (if used) - AuthenticationProperties authProps = new() { + AuthenticationProperties authProps = new() + { RedirectUri = redirectUrl, IsPersistent = true, }; await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme, authProps); // Manually remove the authentication cookie // Delete each cookie - foreach (string cookieName in Request.Cookies.Keys) { + foreach (string cookieName in Request.Cookies.Keys) + { Response.Cookies.Delete(cookieName); } return Ok(new { redirectUrl }); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs index 82ef210..aff926f 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs @@ -1,36 +1,45 @@ -using Evently.Server.Domains.Entities; +using System.Security.Claims; +using Evently.Server.Domains.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; -using System.Security.Claims; namespace Evently.Server.Features.Accounts.Services; // Based on https://tinyurl.com/5cxw9vmu public sealed class AccountAuthorizationHandler(UserManager userManager) - : AuthorizationHandler { + : AuthorizationHandler +{ protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, SameAccountRequirement requirement, - string? identityUserId) { + string? identityUserId + ) + { ClaimsPrincipal principal = context.User; Account? user = await FindByClaimsPrincipalAsync(userManager, principal); bool userMatch = user is not null && user.Id == identityUserId; - if (userMatch) { + if (userMatch) + { context.Succeed(requirement); } } - private static async Task FindByClaimsPrincipalAsync(UserManager userManager, - ClaimsPrincipal claimsPrincipal) { + private static async Task FindByClaimsPrincipalAsync( + UserManager userManager, + ClaimsPrincipal claimsPrincipal + ) + { string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; - string providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; Account? user = await userManager.GetUserAsync(claimsPrincipal); return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); } } -public class SameAccountRequirement : IAuthorizationRequirement { +public class SameAccountRequirement : IAuthorizationRequirement +{ public const string PolicyName = "SameAccountPolicy"; -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs b/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs index 574a128..b289a33 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs @@ -1,27 +1,35 @@ -using Microsoft.AspNetCore.Authentication; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; namespace Evently.Server.Features.Accounts.Services; -public static class AccountExtensions { - public static async Task IsResourceOwner(this ControllerBase controller, object? resourceIdentityUserId) { +public static class AccountExtensions +{ + public static async Task IsResourceOwner( + this ControllerBase controller, + object? resourceIdentityUserId + ) + { IAuthorizationService authorizationService = controller.HttpContext.RequestServices.GetRequiredService(); - AuthenticateResult authenticationResult = - await controller.HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); - if (!authenticationResult.Succeeded || resourceIdentityUserId is null) { + AuthenticateResult authenticationResult = await controller.HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!authenticationResult.Succeeded || resourceIdentityUserId is null) + { return false; } ClaimsPrincipal principal = authenticationResult.Principal ?? new ClaimsPrincipal(); - AuthorizationResult authorizationResult = - await authorizationService.AuthorizeAsync(principal, - resourceIdentityUserId, - SameAccountRequirement.PolicyName); + AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync( + principal, + resourceIdentityUserId, + SameAccountRequirement.PolicyName + ); return authorizationResult.Succeeded; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Accounts/Services/AccountService.cs b/src/Evently.Server/Features/Accounts/Services/AccountService.cs index f679b85..adb8b1e 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountService.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountService.cs @@ -1,45 +1,59 @@ -using Evently.Server.Domains.Entities; +using System.Security.Claims; +using System.Text.RegularExpressions; +using Evently.Server.Domains.Entities; using Evently.Server.Domains.Exceptions; using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Identity; -using System.Security.Claims; -using System.Text.RegularExpressions; namespace Evently.Server.Features.Accounts.Services; // Based on https://tinyurl.com/4u4r7ywy -public sealed partial class AccountService(UserManager userManager) : IAccountsService { - public async Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider) { - string providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; +public sealed partial class AccountService(UserManager userManager) : IAccountsService +{ + public async Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider) + { + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; UserLoginInfo info = new(loginProvider, providerKey, loginProvider); - Account user = await userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey) - ?? await CreateExternalUser(claimsPrincipal, loginProvider); + Account user = + await userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey) + ?? await CreateExternalUser(claimsPrincipal, loginProvider); await userManager.AddLoginAsync(user, info); return user; } - public async Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal) { + public async Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal) + { string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; - string providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; Account? user = await userManager.GetUserAsync(claimsPrincipal); return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); } - private async Task CreateExternalUser(ClaimsPrincipal claimsPrincipal, string loginProvider) { - UserLoginInfo info = new(loginProvider, + private async Task CreateExternalUser( + ClaimsPrincipal claimsPrincipal, + string loginProvider + ) + { + UserLoginInfo info = new( + loginProvider, providerKey: claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty, - loginProvider); + loginProvider + ); string? email = claimsPrincipal.FindFirstValue(ClaimTypes.Email); - if (email == null) { + if (email == null) + { throw new ExternalLoginProviderException(loginProvider, "Email is null"); } string username = claimsPrincipal.FindFirstValue(ClaimTypes.Name) ?? string.Empty; username = UsernameRegex().Replace(username, ""); - Account newUser = new() { + Account newUser = new() + { UserName = username, Email = email, EmailConfirmed = true, @@ -47,17 +61,23 @@ private async Task CreateExternalUser(ClaimsPrincipal claimsPrincipal, IdentityResult result = await userManager.CreateAsync(newUser); - if (!result.Succeeded) { - throw new ExternalLoginProviderException(loginProvider, + if (!result.Succeeded) + { + throw new ExternalLoginProviderException( + loginProvider, message: $"Unable to create user: {string.Join(", ", - values: result.Errors.Select(x => x.Description))}"); + values: result.Errors.Select(x => x.Description))}" + ); } IdentityResult loginResult = await userManager.AddLoginAsync(newUser, info); - if (!loginResult.Succeeded) { - throw new ExternalLoginProviderException(loginProvider, + if (!loginResult.Succeeded) + { + throw new ExternalLoginProviderException( + loginProvider, message: $"Unable to login user: {string.Join(", ", - values: loginResult.Errors.Select(err => err.Description))}"); + values: loginResult.Errors.Select(err => err.Description))}" + ); } return newUser; @@ -65,4 +85,4 @@ private async Task CreateExternalUser(ClaimsPrincipal claimsPrincipal, [GeneratedRegex("[^a-zA-Z0-9]")] private static partial Regex UsernameRegex(); -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs index 48b6ed4..c88c199 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs @@ -3,18 +3,26 @@ namespace Evently.Server.Features.Accounts.Services; -public sealed class AccountValidator : AbstractValidator { - public AccountValidator() { +public sealed class AccountValidator : AbstractValidator +{ + public AccountValidator() + { RuleFor((member) => member.Name).NotEmpty().WithMessage("Name is required."); RuleFor((member) => member.Email).NotEmpty().WithMessage("Email is required."); - RuleForEach((member) => member.Bookings).Custom((value, context) => { - if (value.AttendeeId == string.Empty) { - context.AddFailure("MemberId is required."); - } + RuleForEach((member) => member.Bookings) + .Custom( + (value, context) => + { + if (value.AttendeeId == string.Empty) + { + context.AddFailure("MemberId is required."); + } - if (string.IsNullOrEmpty(value.BookingId)) { - context.AddFailure("BookingId is required."); - } - }); + if (string.IsNullOrEmpty(value.BookingId)) + { + context.AddFailure("BookingId is required."); + } + } + ); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs index aac0060..8690990 100644 --- a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs +++ b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs @@ -1,11 +1,11 @@ +using System.Globalization; +using System.Threading.Channels; using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; using Evently.Server.Features.Accounts.Services; using Microsoft.AspNetCore.Mvc; -using System.Globalization; -using System.Threading.Channels; namespace Evently.Server.Features.Bookings.Controllers; @@ -14,12 +14,15 @@ namespace Evently.Server.Features.Bookings.Controllers; public sealed class BookingsController( IBookingService bookingService, ChannelWriter emailQueue, - ILogger logger) - : ControllerBase { + ILogger logger +) : ControllerBase +{ [HttpGet("{bookingId}", Name = "GetBooking")] - public async Task> GetBooking(string bookingId) { + public async Task> GetBooking(string bookingId) + { Booking? booking = await bookingService.GetBooking(bookingId); - if (booking is null) { + if (booking is null) + { return NotFound(); } @@ -27,7 +30,8 @@ public async Task> GetBooking(string bookingId) { } [HttpGet("{bookingId}/preview", Name = "PreviewBooking")] - public async Task> PreviewBooking(string bookingId) { + public async Task> PreviewBooking(string bookingId) + { string html = await bookingService.RenderTicket(bookingId); return Content(html, "text/html"); } @@ -38,12 +42,17 @@ public async Task> GetBookings( long? gatheringId, DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, bool isCancelled, int? offset, - int? limit) { - PageResult result = await bookingService.GetBookings(attendeeId, + int? limit + ) + { + PageResult result = await bookingService.GetBookings( + attendeeId, gatheringId, checkInStart, checkInEnd, @@ -53,55 +62,72 @@ public async Task> GetBookings( gatheringEndAfter, isCancelled, offset, - limit); + limit + ); List bookingEvents = result.Items; int total = result.TotalCount; HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append("X-Total-Count", value: total.ToString(CultureInfo.InvariantCulture)); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); return Ok(bookingEvents); } [HttpPost("", Name = "CreateBooking")] - public async Task> CreateBooking([FromBody] BookingReqDto bookingReqDto) { + public async Task> CreateBooking([FromBody] BookingReqDto bookingReqDto) + { Booking booking = await bookingService.CreateBooking(bookingReqDto); await emailQueue.WriteAsync(booking.BookingId); return Ok(booking); } [HttpPatch("{bookingId}/cancel", Name = "CancelBooking")] - public async Task CancelBooking(string bookingId) { + public async Task CancelBooking(string bookingId) + { Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Gathering is null) { + if (booking?.Gathering is null) + { return NotFound(); } bool isAuth = await this.IsResourceOwner(booking.AttendeeId); logger.LogInformation("isAuth: {}", isAuth); - if (!isAuth) { + if (!isAuth) + { return Forbid(); } booking.CancellationDateTime = DateTimeOffset.UtcNow; - booking = await bookingService.UpdateBooking(bookingId, bookingReqDto: booking.ToBookingDto()); + booking = await bookingService.UpdateBooking( + bookingId, + bookingReqDto: booking.ToBookingDto() + ); return Ok(booking); } [HttpPatch("{bookingId}/checkIn", Name = "CheckInBooking")] - public async Task CheckInBooking(string bookingId) { + public async Task CheckInBooking(string bookingId) + { Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Gathering is null) { + if (booking?.Gathering is null) + { return NotFound(); } Gathering gathering = booking.Gathering; bool isAuth = await this.IsResourceOwner(gathering.OrganiserId); logger.LogInformation("isAuth: {}", isAuth); - if (!isAuth) { + if (!isAuth) + { return Forbid(); } booking.CheckInDateTime = DateTimeOffset.UtcNow; - booking = await bookingService.UpdateBooking(bookingId, bookingReqDto: booking.ToBookingDto()); + booking = await bookingService.UpdateBooking( + bookingId, + bookingReqDto: booking.ToBookingDto() + ); return Ok(booking); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index e26bec2..7965276 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; @@ -8,7 +9,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NanoidDotNet; -using System.Text.Json; using ValidationResult = FluentValidation.Results.ValidationResult; namespace Evently.Server.Features.Bookings.Services; @@ -18,40 +18,65 @@ public sealed class BookingService( IObjectStorageService objectStorageService, IValidator validator, IOptions settings, - AppDbContext db) - : IBookingService { + AppDbContext db +) : IBookingService +{ private readonly string _containerName = settings.Value.StorageAccount.AccountName; - public async Task GetBooking(string bookingId) { - return await db.Bookings - .Include((b) => b.Account) + public async Task GetBooking(string bookingId) + { + return await db + .Bookings.Include((b) => b.Account) .Include((b) => b.Gathering) - .ThenInclude((g) => g!.GatheringCategoryDetails) - .ThenInclude((detail) => detail.Category) + .ThenInclude((g) => g!.GatheringCategoryDetails) + .ThenInclude((detail) => detail.Category) .FirstOrDefaultAsync((be) => be.BookingId == bookingId); } - public async Task> GetBookings(string? accountId, long? gatheringId, - DateTimeOffset? checkInStart, DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, + public async Task> GetBookings( + string? accountId, + long? gatheringId, + DateTimeOffset? checkInStart, + DateTimeOffset? checkInEnd, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, - bool? isCancelled, int? offset, int? limit) { - IQueryable query = db.Bookings - .Where((b) => accountId == null || b.AttendeeId == accountId) + bool? isCancelled, + int? offset, + int? limit + ) + { + IQueryable query = db + .Bookings.Where((b) => accountId == null || b.AttendeeId == accountId) .Where((b) => gatheringId == null || b.GatheringId == gatheringId) .Where((c) => checkInStart == null || checkInStart <= c.CheckInDateTime) .Where((b) => checkInEnd == null || b.CheckInDateTime <= checkInEnd) .Where((b) => isCancelled == null || b.CancellationDateTime.HasValue == isCancelled) - .Where((b) => - gatheringStartBefore == null || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore) - .Where((b) => - gatheringStartAfter == null || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter) - .Where((b) => gatheringEndBefore == null || b.Gathering != null && b.Gathering.End <= gatheringEndBefore) - .Where((b) => gatheringEndAfter == null || b.Gathering != null && b.Gathering.End >= gatheringEndAfter) + .Where( + (b) => + gatheringStartBefore == null + || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore + ) + .Where( + (b) => + gatheringStartAfter == null + || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter + ) + .Where( + (b) => + gatheringEndBefore == null + || b.Gathering != null && b.Gathering.End <= gatheringEndBefore + ) + .Where( + (b) => + gatheringEndAfter == null + || b.Gathering != null && b.Gathering.End >= gatheringEndAfter + ) .Include((b) => b.Account) .Include((b) => b.Gathering) - .ThenInclude((g) => g!.GatheringCategoryDetails) - .ThenInclude((detail) => detail.Category); + .ThenInclude((g) => g!.GatheringCategoryDetails) + .ThenInclude((detail) => detail.Category); int totalCount = await query.CountAsync(); @@ -61,18 +86,18 @@ public async Task> GetBookings(string? accountId, long? gath .Take(limit ?? int.MaxValue) .ToListAsync(); - return new PageResult { - Items = bookingEvents, - TotalCount = totalCount, - }; + return new PageResult { Items = bookingEvents, TotalCount = totalCount }; } - public async Task CreateBooking(BookingReqDto bookingReqDto) { + public async Task CreateBooking(BookingReqDto bookingReqDto) + { Booking booking = bookingReqDto.ToBooking(); ValidationResult validationResult = await validator.ValidateAsync(booking); - if (!validationResult.IsValid) { + if (!validationResult.IsValid) + { throw new ArgumentException( - $"Account has already booked this gathering (GatheringId: {booking.GatheringId})"); + $"Account has already booked this gathering (GatheringId: {booking.GatheringId})" + ); } booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}"; @@ -81,17 +106,21 @@ public async Task CreateBooking(BookingReqDto bookingReqDto) { return (await GetBooking(booking.BookingId))!; } - public async Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto) { + public async Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto) + { Booking booking = bookingReqDto.ToBooking(); ValidationResult validationResult = await validator.ValidateAsync(booking); - if (!validationResult.IsValid) { - throw new ArgumentException(string.Join("\n", values: validationResult.Errors.Select(e => e.ErrorMessage))); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join("\n", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); } - Booking current = await db.Bookings.AsTracking() - .FirstOrDefaultAsync((be) => be.BookingId == bookingId) - ?? throw new KeyNotFoundException($"{booking.BookingId} not found"); + Booking current = + await db.Bookings.AsTracking().FirstOrDefaultAsync((be) => be.BookingId == bookingId) + ?? throw new KeyNotFoundException($"{booking.BookingId} not found"); current.AttendeeId = booking.AttendeeId; current.GatheringId = booking.GatheringId; @@ -104,11 +133,14 @@ public async Task UpdateBooking(string bookingId, BookingReqDto booking return (await GetBooking(booking.BookingId))!; } - public async Task RenderTicket(string bookingId) { + public async Task RenderTicket(string bookingId) + { Booking? booking = await GetBooking(bookingId); - if (booking?.Account is null || booking.Gathering is null) { + if (booking?.Account is null || booking.Gathering is null) + { throw new KeyNotFoundException( - $"Booking with id: {bookingId} not found or related member or gathering is null"); + $"Booking with id: {bookingId} not found or related member or gathering is null" + ); } string qrData = JsonSerializer.Serialize(new { bookingEventId = bookingId }); @@ -117,17 +149,26 @@ public async Task RenderTicket(string bookingId) { Uri uri; bool isFileExists = await objectStorageService.IsFileExists(_containerName, fileName); - if (!isFileExists) { - uri = await objectStorageService.UploadFile(_containerName, fileName, binaryData, "image/png"); - } else { + if (!isFileExists) + { + uri = await objectStorageService.UploadFile( + _containerName, + fileName, + binaryData, + "image/png" + ); + } + else + { uri = await objectStorageService.GetFileUri(_containerName, fileName); } - Dictionary props = new() { + Dictionary props = new() + { { "Booking", booking }, { "QrCodeUrl", uri.AbsoluteUri }, }; return await mediaRenderer.RenderComponentHtml(props); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs index 223b6d1..c2bf992 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs @@ -3,9 +3,13 @@ namespace Evently.Server.Features.Bookings.Services; -public sealed class BookingValidator : AbstractValidator { - public BookingValidator() { - RuleFor((booking) => booking.GatheringId).NotEmpty().WithMessage("GatheringId is required."); +public sealed class BookingValidator : AbstractValidator +{ + public BookingValidator() + { + RuleFor((booking) => booking.GatheringId) + .NotEmpty() + .WithMessage("GatheringId is required."); RuleFor((booking) => booking.AttendeeId).NotEmpty().WithMessage("AccountId is required."); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs index f6bf919..6756b17 100644 --- a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs +++ b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs @@ -1,27 +1,33 @@ -using Evently.Server.Domains.Entities; +using System.Globalization; +using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; using Microsoft.AspNetCore.Mvc; -using System.Globalization; namespace Evently.Server.Features.Categories.Controllers; [ApiController] [Route("api/v1/[controller]")] -public sealed class CategoriesController(ICategoryService categoryService) : ControllerBase { +public sealed class CategoriesController(ICategoryService categoryService) : ControllerBase +{ [HttpGet(Name = "GetCategories")] - public async Task>> GetCategories(long? memberId, bool? approved) { + public async Task>> GetCategories(long? memberId, bool? approved) + { PageResult result = await categoryService.GetCategories(memberId, approved); List topics = result.Items; int total = result.TotalCount; HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append("X-Total-Count", value: total.ToString(CultureInfo.InvariantCulture)); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); return Ok(topics); } [HttpPost("", Name = "CreateCategory")] - public async Task> CreateCategory(Category category) { + public async Task> CreateCategory(Category category) + { category = await categoryService.CreateCategory(category); return Ok(category); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Categories/Services/CategoryService.cs b/src/Evently.Server/Features/Categories/Services/CategoryService.cs index 6258dba..4442f99 100644 --- a/src/Evently.Server/Features/Categories/Services/CategoryService.cs +++ b/src/Evently.Server/Features/Categories/Services/CategoryService.cs @@ -6,27 +6,31 @@ namespace Evently.Server.Features.Categories.Services; -public sealed class CategoryService(AppDbContext db) : ICategoryService { - public async Task CreateCategory(Category category) { +public sealed class CategoryService(AppDbContext db) : ICategoryService +{ + public async Task CreateCategory(Category category) + { await db.Categories.AddAsync(category); await db.SaveChangesAsync(); return category; } - public async Task> GetCategories(long? gatheringId, bool? approved) { - IQueryable query = db.Categories - .Include((category) => category.GatheringCategoryDetails) - .Where((category) => - gatheringId == null || - category.GatheringCategoryDetails.Any((detail) => detail.GatheringId == gatheringId)) + public async Task> GetCategories(long? gatheringId, bool? approved) + { + IQueryable query = db + .Categories.Include((category) => category.GatheringCategoryDetails) + .Where( + (category) => + gatheringId == null + || category.GatheringCategoryDetails.Any( + (detail) => detail.GatheringId == gatheringId + ) + ) .Where((category) => approved == null || category.Approved == approved); int totalCount = await query.CountAsync(); List topics = await query.ToListAsync(); - return new PageResult { - Items = topics, - TotalCount = totalCount, - }; + return new PageResult { Items = topics, TotalCount = totalCount }; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs index 3cfa795..bba3c5e 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs @@ -7,34 +7,50 @@ namespace Evently.Server.Features.Emails.Services; -public sealed class EmailAdapter(ILogger logger, IOptions settings) : IEmailerAdapter { +public sealed class EmailAdapter(ILogger logger, IOptions settings) + : IEmailerAdapter +{ private readonly string _from = settings.Value.EmailSettings.ActualFrom; private readonly string _password = settings.Value.EmailSettings.SmtpPassword; - public async Task SendEmailAsync(string senderEmail, string recipientEmail, string subject, string body) { + public async Task SendEmailAsync( + string senderEmail, + string recipientEmail, + string subject, + string body + ) + { MimeMessage emailMessage = CreateMessage(senderEmail, recipientEmail, subject, body); - try { + try + { await SendEmail(emailMessage); - } catch (Exception ex) { + } + catch (Exception ex) + { logger.LogCallbackUrl(ex.Message); } } - private static MimeMessage CreateMessage(string senderEmail, string recipientEmail, string subject, string body) { + private static MimeMessage CreateMessage( + string senderEmail, + string recipientEmail, + string subject, + string body + ) + { MimeMessage emailMessage = new(); emailMessage.From.Add(new MailboxAddress(senderEmail, senderEmail)); emailMessage.To.Add(new MailboxAddress(recipientEmail, recipientEmail)); emailMessage.Subject = subject; - BodyBuilder bodyBuilder = new() { - HtmlBody = body, - }; + BodyBuilder bodyBuilder = new() { HtmlBody = body }; emailMessage.Body = bodyBuilder.ToMessageBody(); return emailMessage; } - private async Task SendEmail(MimeMessage emailMessage) { + private async Task SendEmail(MimeMessage emailMessage) + { SmtpClient client = new(); await client.ConnectAsync("smtp.gmail.com", port: 587, useSsl: false); // smtp auth @@ -42,4 +58,4 @@ private async Task SendEmail(MimeMessage emailMessage) { await client.SendAsync(emailMessage); await client.DisconnectAsync(true); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs index cec198b..4c7b1e9 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs @@ -1,7 +1,7 @@ -using Evently.Server.Common.Extensions; +using System.Threading.Channels; +using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; -using System.Threading.Channels; namespace Evently.Server.Features.Emails.Services; @@ -9,28 +9,42 @@ public sealed class EmailBackgroundService( IServiceScopeFactory scopeFactory, ChannelReader reader, IEmailerAdapter emailerAdapter, - ILogger logger) : BackgroundService { - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (await reader.WaitToReadAsync(stoppingToken)) { - try { + ILogger logger +) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (await reader.WaitToReadAsync(stoppingToken)) + { + try + { string bookingId = await reader.ReadAsync(stoppingToken); using IServiceScope scope = scopeFactory.CreateScope(); - IBookingService bookingService = scope.ServiceProvider.GetRequiredService(); + IBookingService bookingService = + scope.ServiceProvider.GetRequiredService(); Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Account?.Email is null) { + if (booking?.Account?.Email is null) + { continue; } Account account = booking.Account; string html = await bookingService.RenderTicket(bookingId); - await emailerAdapter.SendEmailAsync("noreply@evently", account.Email, "Test QR ticket", html); + await emailerAdapter.SendEmailAsync( + "noreply@evently", + account.Email, + "Test QR ticket", + html + ); logger.LogSuccessEmail(account.Email); - } catch (Exception ex) { + } + catch (Exception ex) + { logger.LogError("email error: {}", ex.Message); } } } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs index c91b59f..7cc17be 100644 --- a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs +++ b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs @@ -9,9 +9,13 @@ namespace Evently.Server.Features.Emails.Services; -public sealed class MediaRenderer(BlazorHtmlRenderer htmlRenderer) : IMediaRenderer { - public async Task RenderComponentHtml(Dictionary dictionary) where T : IComponent { - string html = await htmlRenderer.Dispatcher.InvokeAsync(async () => { +public sealed class MediaRenderer(BlazorHtmlRenderer htmlRenderer) : IMediaRenderer +{ + public async Task RenderComponentHtml(Dictionary dictionary) + where T : IComponent + { + string html = await htmlRenderer.Dispatcher.InvokeAsync(async () => + { ParameterView parameters = ParameterView.FromDictionary(dictionary); HtmlRootComponent output = await htmlRenderer.RenderComponentAsync(parameters); return output.ToHtmlString(); @@ -19,7 +23,8 @@ public async Task RenderComponentHtml(Dictionary dic return html; } - public BinaryData RenderQr(string qrData) { + public BinaryData RenderQr(string qrData) + { using QRCodeGenerator qrGenerator = new(); QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrData, QRCodeGenerator.ECCLevel.Q); PngByteQRCode qrCode = new(qrCodeData); @@ -27,11 +32,12 @@ public BinaryData RenderQr(string qrData) { return BinaryData.FromBytes(imageBytes); } - public BinaryData RenderPdf(string html) { + public BinaryData RenderPdf(string html) + { using MemoryStream ms = new(); using PdfDocument pdf = PdfGenerator.GeneratePdf(html, PageSize.A4); pdf.Save(ms); byte[] bytes = ms.ToArray(); return BinaryData.FromBytes(bytes); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Files/Controllers/FilesController.cs b/src/Evently.Server/Features/Files/Controllers/FilesController.cs index a42ca30..9dfb2b3 100644 --- a/src/Evently.Server/Features/Files/Controllers/FilesController.cs +++ b/src/Evently.Server/Features/Files/Controllers/FilesController.cs @@ -1,36 +1,55 @@ -using Evently.Server.Domains.Interfaces; +using System.ComponentModel.DataAnnotations; +using Evently.Server.Domains.Interfaces; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; -using System.ComponentModel.DataAnnotations; namespace Evently.Server.Features.Files.Controllers; [ApiController] [Route("api/v1/[controller]")] -public class FilesController(ILogger logger, IObjectStorageService objectStorageService) - : ControllerBase { +public class FilesController( + ILogger logger, + IObjectStorageService objectStorageService +) : ControllerBase +{ [HttpGet("object-storage/{bucket}", Name = "GetFile")] - public async Task GetFile(string bucket, [Required] [FromQuery] string fileName) { + public async Task GetFile(string bucket, [Required] [FromQuery] string fileName) + { logger.LogInformation("fileName: {}", fileName); - try { + try + { BinaryData binaryData = await objectStorageService.GetFile(bucket, fileName); logger.LogInformation("binaryData.MediaType: {}", binaryData.MediaType); string contentType = binaryData.MediaType ?? GetContentType(fileName); - return File(fileContents: binaryData.ToArray(), fileDownloadName: fileName, contentType: contentType); - } catch (Exception ex) { - return ex switch { - FileNotFoundException => NotFound($"File '{fileName}' not found in bucket '{bucket}'."), - _ => StatusCode(statusCode: 500, "An unexpected error occurred while retrieving the file."), + return File( + fileContents: binaryData.ToArray(), + fileDownloadName: fileName, + contentType: contentType + ); + } + catch (Exception ex) + { + return ex switch + { + FileNotFoundException => NotFound( + $"File '{fileName}' not found in bucket '{bucket}'." + ), + _ => StatusCode( + statusCode: 500, + "An unexpected error occurred while retrieving the file." + ), }; } } - private static string GetContentType(string fileName) { + private static string GetContentType(string fileName) + { FileExtensionContentTypeProvider provider = new(); - if (!provider.TryGetContentType(fileName, contentType: out string? contentType)) { + if (!provider.TryGetContentType(fileName, contentType: out string? contentType)) + { contentType = "application/octet-stream"; // Default fallback } return contentType; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 17e3892..cfc940a 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -10,38 +10,53 @@ namespace Evently.Server.Features.Files.Services; // Based on https://tinyurl.com/5pam66xn -public sealed class ObjectStorageService : IObjectStorageService { +public sealed class ObjectStorageService : IObjectStorageService +{ private readonly BlobServiceClient _blobServiceClient; private readonly ContentSafetyClient? _contentSafetyClient; private readonly ILogger _logger; - public ObjectStorageService(IOptions settings, ILogger logger) { + public ObjectStorageService(IOptions settings, ILogger logger) + { _logger = logger; - _blobServiceClient = - new BlobServiceClient(settings.Value.StorageAccount.AzureStorageConnectionString); + _blobServiceClient = new BlobServiceClient( + settings.Value.StorageAccount.AzureStorageConnectionString + ); - try { + try + { _contentSafetyClient = new ContentSafetyClient( endpoint: new Uri(settings.Value.AzureAiFoundry.ContentSafetyEndpoint), - credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey)); - } catch (Exception ex) { + credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey) + ); + } + catch (Exception ex) + { // silence the error - _logger.LogError("error creating content safety client: {message}. Content moderation skipped.", - ex.Message); + _logger.LogError( + "error creating content safety client: {message}. Content moderation skipped.", + ex.Message + ); } } - public async Task UploadFile(string containerName, string fileName, BinaryData binaryData, - string mimeType = "application/octet-stream") { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + public async Task UploadFile( + string containerName, + string fileName, + BinaryData binaryData, + string mimeType = "application/octet-stream" + ) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); await containerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer); BlobClient blobClient = containerClient.GetBlobClient(fileName); - BlobUploadOptions uploadOptions = new() { - HttpHeaders = new BlobHttpHeaders { - ContentType = mimeType, - }, + BlobUploadOptions uploadOptions = new() + { + HttpHeaders = new BlobHttpHeaders { ContentType = mimeType }, // https://tinyurl.com/ms4hvsta // By default, there will be condition to prevent overwrite. // Set the conditions to null so that overwrite will be allowed. @@ -51,32 +66,45 @@ public async Task UploadFile(string containerName, string fileName, BinaryD return blobClient.Uri; } - public async Task IsFileExists(string containerName, string fileName) { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + public async Task IsFileExists(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); BlobClient blobClient = containerClient.GetBlobClient(fileName); Response result = await blobClient.ExistsAsync(); return result.Value; } - public Task GetFileUri(string containerName, string fileName) { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + public Task GetFileUri(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); BlobClient blobClient = containerClient.GetBlobClient(fileName); return Task.FromResult(blobClient.Uri); } - public async Task GetFile(string containerName, string fileName) { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + public async Task GetFile(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); BlobClient blobClient = containerClient.GetBlobClient(fileName); Response result = await blobClient.ExistsAsync(); - if (!result.Value) { + if (!result.Value) + { throw new FileNotFoundException($"File {fileName} not found"); } using MemoryStream ms = new(); - try { + try + { await blobClient.DownloadToAsync(ms); - } catch (Exception ex) { + } + catch (Exception ex) + { _logger.LogError("error getting file: {}", ex.Message); } @@ -85,26 +113,31 @@ public async Task GetFile(string containerName, string fileName) { return data; } - public async Task PassesContentModeration(BinaryData binaryData) { - if (_contentSafetyClient is null) { + public async Task PassesContentModeration(BinaryData binaryData) + { + if (_contentSafetyClient is null) + { return true; } ContentSafetyImageData image = new(binaryData); AnalyzeImageOptions request = new(image); Response response; - try { + try + { response = await _contentSafetyClient.AnalyzeImageAsync(request); - } catch (RequestFailedException ex) { + } + catch (RequestFailedException ex) + { _logger.LogContentModerationError(ex.Status.ToString(), ex.ErrorCode ?? "", ex.Message); throw; } AnalyzeImageResult result = response.Value; - int dangerScore = result.CategoriesAnalysis - .Select(v => v.Severity ?? 0) + int dangerScore = result + .CategoriesAnalysis.Select(v => v.Severity ?? 0) .DefaultIfEmpty(0) .Aggregate((a, b) => a + b); return dangerScore == 0; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs index a630343..3fb557c 100644 --- a/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs +++ b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs @@ -1,4 +1,5 @@ -using Evently.Server.Common.Extensions; +using System.Globalization; +using Evently.Server.Common.Extensions; using Evently.Server.Domains.Entities; using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; @@ -8,7 +9,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using MimeKit; -using System.Globalization; namespace Evently.Server.Features.Gatherings.Controllers; @@ -18,13 +18,17 @@ public sealed class GatheringsController( IOptions settings, ILogger logger, IGatheringService gatheringService, - IObjectStorageService objectStorageService) : ControllerBase { + IObjectStorageService objectStorageService +) : ControllerBase +{ private readonly string _containerName = settings.Value.StorageAccount.AccountName; [HttpGet("{gatheringId:long}", Name = "GetGathering")] - public async Task> GetGathering(long gatheringId) { + public async Task> GetGathering(long gatheringId) + { Gathering? customer = await gatheringService.GetGathering(gatheringId); - if (customer is null) { + if (customer is null) + { return NotFound(); } @@ -32,16 +36,23 @@ public async Task> GetGathering(long gatheringId) { } [HttpGet("", Name = "GetGatherings")] - public async Task>> GetGatherings(string? attendeeId, + public async Task>> GetGatherings( + string? attendeeId, string? organiserId, string? name, - DateTimeOffset? startDateBefore, DateTimeOffset? startDateAfter, DateTimeOffset? endDateBefore, + DateTimeOffset? startDateBefore, + DateTimeOffset? startDateAfter, + DateTimeOffset? endDateBefore, DateTimeOffset? endDateAfter, bool? isCancelled, [FromQuery(Name = "categoryIds[]")] long[]? categoryIds, - int? offset, int? limit) { + int? offset, + int? limit + ) + { logger.LogInformation("categoryIds: {}", string.Join(",", values: categoryIds ?? [])); - PageResult result = await gatheringService.GetGatherings(attendeeId, + PageResult result = await gatheringService.GetGatherings( + attendeeId, organiserId, name, startDateBefore, @@ -51,26 +62,36 @@ public async Task>> GetGatherings(string? attendeeI isCancelled, categoryIds: categoryIds?.ToHashSet(), offset, - limit); + limit + ); List exhibitions = result.Items; int total = result.TotalCount; HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append("X-Total-Count", value: total.ToString(CultureInfo.InvariantCulture)); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); return Ok(exhibitions); } [HttpPost("", Name = "CreateGathering")] - public async Task> CreateGathering([FromForm] GatheringReqDto gatheringReqDto, - [FromForm] IFormFile? coverImg) { + public async Task> CreateGathering( + [FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg + ) + { gatheringReqDto = gatheringReqDto with { GatheringId = 0L }; - AuthenticateResult authenticationResult = - await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); - if (!authenticationResult.Succeeded) { + AuthenticateResult authenticationResult = await HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!authenticationResult.Succeeded) + { return Unauthorized(); } - if (coverImg != null) { + if (coverImg != null) + { string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; } @@ -80,18 +101,25 @@ public async Task> CreateGathering([FromForm] GatheringR } [HttpPut("{gatheringId:long}", Name = "UpdateGathering")] - public async Task UpdateGathering(long gatheringId, [FromForm] GatheringReqDto gatheringReqDto, - [FromForm] IFormFile? coverImg) { + public async Task UpdateGathering( + long gatheringId, + [FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg + ) + { Gathering? gathering = await gatheringService.GetGathering(gatheringId); - if (gathering is null) { + if (gathering is null) + { return NotFound(); } - if (!await this.IsResourceOwner(gathering.OrganiserId)) { + if (!await this.IsResourceOwner(gathering.OrganiserId)) + { return Forbid(); } - if (coverImg != null) { + if (coverImg != null) + { string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; } @@ -101,13 +129,16 @@ public async Task UpdateGathering(long gatheringId, [FromForm] Gat } [HttpDelete("{gatheringId:long}", Name = "DeleteGathering")] - public async Task> DeleteGathering(long gatheringId) { + public async Task> DeleteGathering(long gatheringId) + { Gathering? exhibition = await gatheringService.GetGathering(gatheringId); - if (exhibition is null) { + if (exhibition is null) + { return NotFound(); } - if (!await this.IsResourceOwner(exhibition.OrganiserId)) { + if (!await this.IsResourceOwner(exhibition.OrganiserId)) + { return Forbid(); } @@ -115,18 +146,23 @@ public async Task> DeleteGathering(long gatheringId) { return NoContent(); } - private async Task UploadCoverImage(long gatheringId, IFormFile coverImg) { - string fileName = $"gatherings/{gatheringId}/cover-image{Path.GetExtension(coverImg.FileName)}"; + private async Task UploadCoverImage(long gatheringId, IFormFile coverImg) + { + string fileName = + $"gatherings/{gatheringId}/cover-image{Path.GetExtension(coverImg.FileName)}"; BinaryData binaryData = await coverImg.ToBinaryData(); bool isContentSafe = await objectStorageService.PassesContentModeration(binaryData); - if (!isContentSafe) { + if (!isContentSafe) + { return string.Empty; } - Uri uri = await objectStorageService.UploadFile(_containerName, + Uri uri = await objectStorageService.UploadFile( + _containerName, fileName, binaryData, - mimeType: MimeTypes.GetMimeType(coverImg.FileName)); + mimeType: MimeTypes.GetMimeType(coverImg.FileName) + ); return uri.AbsoluteUri; } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index 4509024..24a4db4 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -9,12 +9,15 @@ namespace Evently.Server.Features.Gatherings.Services; -public sealed class GatheringService(AppDbContext db, IValidator validator) : IGatheringService { - public async Task GetGathering(long gatheringId) { - return await db.Gatherings - .Include(gathering => gathering.Bookings) +public sealed class GatheringService(AppDbContext db, IValidator validator) + : IGatheringService +{ + public async Task GetGathering(long gatheringId) + { + return await db + .Gatherings.Include(gathering => gathering.Bookings) .Include(gathering => gathering.GatheringCategoryDetails) - .ThenInclude(detail => detail.Category) + .ThenInclude(detail => detail.Category) .FirstOrDefaultAsync((gathering) => gathering.GatheringId == gatheringId); } @@ -29,23 +32,37 @@ public async Task> GetGatherings( bool? isCancelled, HashSet? categoryIds, int? offset, - int? limit) { - IQueryable query = db.Gatherings - .Where((gathering) => name == null || EF.Functions.Like(gathering.Name, $"%{name}%")) + int? limit + ) + { + IQueryable query = db + .Gatherings.Where( + (gathering) => name == null || EF.Functions.Like(gathering.Name, $"%{name}%") + ) .Where((gathering) => startDateBefore == null || gathering.Start <= startDateBefore) .Where((gathering) => startDateAfter == null || gathering.Start >= startDateAfter) .Where((gathering) => endDateBefore == null || gathering.End <= endDateBefore) .Where((gathering) => endDateAfter == null || gathering.End >= endDateAfter) .Where((gathering) => organiserId == null || gathering.OrganiserId == organiserId) - .Where(gathering => isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled) - .Where((gathering) => - categoryIds == null || categoryIds.Count == 0 || - gathering.GatheringCategoryDetails.Any(detail => categoryIds.Contains(detail.CategoryId))) - .Where((gathering) => - attendeeId == null || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId)) + .Where(gathering => + isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled + ) + .Where( + (gathering) => + categoryIds == null + || categoryIds.Count == 0 + || gathering.GatheringCategoryDetails.Any(detail => + categoryIds.Contains(detail.CategoryId) + ) + ) + .Where( + (gathering) => + attendeeId == null + || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId) + ) .Include(gathering => gathering.Bookings.Where((be) => be.AttendeeId == attendeeId)) .Include(gathering => gathering.GatheringCategoryDetails) - .ThenInclude(detail => detail.Category); + .ThenInclude(detail => detail.Category); int totalCount = await query.CountAsync(); @@ -56,17 +73,18 @@ public async Task> GetGatherings( .Select((gathering) => gathering) .ToListAsync(); - return new PageResult { - Items = gatherings, - TotalCount = totalCount, - }; + return new PageResult { Items = gatherings, TotalCount = totalCount }; } - public async Task CreateGathering(GatheringReqDto gatheringReqDto) { + public async Task CreateGathering(GatheringReqDto gatheringReqDto) + { Gathering gathering = gatheringReqDto.ToGathering(); ValidationResult validationResult = await validator.ValidateAsync(gathering); - if (!validationResult.IsValid) { - throw new ArgumentException(string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage))); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); } db.Gatherings.Add(gathering); @@ -74,17 +92,23 @@ public async Task CreateGathering(GatheringReqDto gatheringReqDto) { return gathering; } - public async Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto) { + public async Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto) + { Gathering gathering = gatheringReqDto.ToGathering(); ValidationResult validationResult = await validator.ValidateAsync(gathering); - if (!validationResult.IsValid) { - throw new ArgumentException(string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage))); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); } - Gathering current = await db.Gatherings.AsTracking() - .Include((g) => g.GatheringCategoryDetails) - .FirstOrDefaultAsync((ex) => ex.GatheringId == gatheringId) - ?? throw new KeyNotFoundException($"{gatheringId} not found"); + Gathering current = + await db + .Gatherings.AsTracking() + .Include((g) => g.GatheringCategoryDetails) + .FirstOrDefaultAsync((ex) => ex.GatheringId == gatheringId) + ?? throw new KeyNotFoundException($"{gatheringId} not found"); current.Name = gathering.Name; current.Description = gathering.Description; @@ -98,11 +122,12 @@ public async Task UpdateGathering(long gatheringId, GatheringReqDto g return current; } - public async Task DeleteGathering(long gatheringId) { - Gathering gathering = await db.Gatherings - .AsTracking() + public async Task DeleteGathering(long gatheringId) + { + Gathering gathering = await db + .Gatherings.AsTracking() .SingleAsync((gathering) => gathering.GatheringId == gatheringId); db.Remove(gathering); await db.SaveChangesAsync(); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs index 4971980..594b0ae 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs @@ -3,21 +3,35 @@ namespace Evently.Server.Features.Gatherings.Services; -public sealed class GatheringValidator : AbstractValidator { - public GatheringValidator() { +public sealed class GatheringValidator : AbstractValidator +{ + public GatheringValidator() + { RuleFor((exhibition) => exhibition.Name).NotEmpty().WithMessage("Name is required."); - RuleFor((exhibition) => exhibition.Description).NotEmpty().WithMessage("Description is required."); - RuleFor((exhibition) => exhibition.Start).NotEmpty().WithMessage("Starting Date is required."); + RuleFor((exhibition) => exhibition.Description) + .NotEmpty() + .WithMessage("Description is required."); + RuleFor((exhibition) => exhibition.Start) + .NotEmpty() + .WithMessage("Starting Date is required."); RuleFor((exhibition) => exhibition.End).NotEmpty().WithMessage("End Date is required."); - RuleFor((exhibition) => exhibition.OrganiserId).NotEmpty().WithMessage("Event Organiser Id is required."); - RuleForEach((exhibition) => exhibition.Bookings).Custom((value, context) => { - if (value.GatheringId == 0) { - context.AddFailure("ExhibitionId is required."); - } + RuleFor((exhibition) => exhibition.OrganiserId) + .NotEmpty() + .WithMessage("Event Organiser Id is required."); + RuleForEach((exhibition) => exhibition.Bookings) + .Custom( + (value, context) => + { + if (value.GatheringId == 0) + { + context.AddFailure("ExhibitionId is required."); + } - if (string.IsNullOrEmpty(value.BookingId)) { - context.AddFailure("BookingEventId is required."); - } - }); + if (string.IsNullOrEmpty(value.BookingId)) + { + context.AddFailure("BookingEventId is required."); + } + } + ); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs index 4de00bb..cd9dbac 100644 --- a/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs +++ b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs @@ -5,25 +5,31 @@ namespace Evently.Server.Features.HealthChecks.Controllers; [ApiController] [Route("api/v1/[controller]")] -public sealed class HealthChecksController(HealthCheckService healthCheckService) : ControllerBase { - private readonly Dictionary _statuses = new() { +public sealed class HealthChecksController(HealthCheckService healthCheckService) : ControllerBase +{ + private readonly Dictionary _statuses = new() + { { HealthStatus.Healthy, "Healthy" }, { HealthStatus.Unhealthy, "Unhealthy" }, { HealthStatus.Degraded, "Degraded" }, }; [HttpGet(Name = "HealthCheck")] - public async Task GetHealthcheck() { + public async Task GetHealthcheck() + { HealthReport healthReport = await healthCheckService.CheckHealthAsync(); - Dictionary statuses = healthReport.Entries - .ToDictionary(keySelector: key => key.Key, elementSelector: value => _statuses[value.Value.Status]); + Dictionary statuses = healthReport.Entries.ToDictionary( + keySelector: key => key.Key, + elementSelector: value => _statuses[value.Value.Status] + ); statuses["Server"] = "Healthy"; return Ok(statuses); } [HttpGet("middlewares/error-middleware", Name = "TestErrorMiddleware")] - public Task TestErrorMiddleware() { + public Task TestErrorMiddleware() + { throw new ArgumentException("Test Error Middleware"); } -} \ No newline at end of file +} diff --git a/src/Evently.Server/Program.cs b/src/Evently.Server/Program.cs index eccdc69..0aaeac7 100644 --- a/src/Evently.Server/Program.cs +++ b/src/Evently.Server/Program.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; +using System.Threading.Channels; using Evently.Server.Common.Blazor; using Evently.Server.Common.Data; using Evently.Server.Common.Extensions; @@ -17,17 +19,18 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; -using System.Text.Json.Serialization; -using System.Threading.Channels; -using AccountService=Evently.Server.Features.Accounts.Services.AccountService; -using BlazorHtmlRenderer=Microsoft.AspNetCore.Components.Web.HtmlRenderer; +using AccountService = Evently.Server.Features.Accounts.Services.AccountService; +using BlazorHtmlRenderer = Microsoft.AspNetCore.Components.Web.HtmlRenderer; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); ConfigurationManager config = builder.Configuration; -ILoggerFactory logFactory = LoggerFactory.Create((logBuilder) => { - logBuilder.AddSimpleConsole((opts) => opts.ColorBehavior = LoggerColorBehavior.Disabled); -}); +ILoggerFactory logFactory = LoggerFactory.Create( + (logBuilder) => + { + logBuilder.AddSimpleConsole((opts) => opts.ColorBehavior = LoggerColorBehavior.Disabled); + } +); ILogger logger = logFactory.CreateLogger(); // Inject appsettings.json into the application @@ -36,15 +39,25 @@ // register DB string? dbConnStr = builder.Configuration.GetConnectionString("WebApiDatabase"); logger.LogValue("dbConnStr", dbConnStr); -builder.Services.AddDbContext((options) => { - options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - options.UseSqlServer(dbConnStr, - sqlServerOptionsAction: opt => opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); -}); +builder.Services.AddDbContext( + (options) => + { + options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + options.UseSqlServer( + dbConnStr, + sqlServerOptionsAction: opt => + opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + ); + } +); // Add services to the container. -builder.Services.AddControllersWithViews().AddJsonOptions((options) => - options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles); +builder + .Services.AddControllersWithViews() + .AddJsonOptions( + (options) => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles + ); + // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); @@ -53,8 +66,7 @@ builder.Services.AddTransient(); builder.Services.AddHttpContextAccessor(); builder.Services.AddTransient(); -builder.Services.AddHealthChecks() - .AddDbContextCheck(); +builder.Services.AddHealthChecks().AddDbContextCheck(); builder.Services.AddSingleton(); builder.Services.AddTransient(); @@ -74,64 +86,71 @@ builder.Services.AddScoped, GatheringValidator>(); builder.Services.AddScoped, BookingValidator>(); -builder.Services.AddIdentityApiEndpoints() - .AddEntityFrameworkStores(); +builder.Services.AddIdentityApiEndpoints().AddEntityFrameworkStores(); // https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/7.0/default-authentication-scheme#new-behavior // No default auth scheme is set -builder.Services.AddAuthentication() +builder + .Services.AddAuthentication() .AddCookie() - .AddGoogle((options) => { - options.ClientId = settings.Value.Authentication.Google.ClientId; - options.ClientSecret = settings.Value.Authentication.Google.ClientSecret; - options.CallbackPath = "/api/signin-google"; // rmb to resister in the Google oauth dashboard - options.SignInScheme = - IdentityConstants - .ExternalScheme; // important to default to external scheme - https://stackoverflow.com/a/78674926/6514532 - - // Enable refresh token - options.SaveTokens = true; - options.AccessType = "offline"; - - // For debugging purpose - options.Events.OnRedirectToAuthorizationEndpoint = (context) => { - logger.LogInformation("Request Path: {Request}", context.Request.RootUri().AbsoluteUri); - context.HttpContext.Response.Redirect(context.RedirectUri); - return Task.CompletedTask; - }; - }); - -builder.Services.AddAuthorizationBuilder() - .AddPolicy(SameAccountRequirement.PolicyName, - configurePolicy: (policy) => - policy.Requirements.Add(new SameAccountRequirement())); + .AddGoogle( + (options) => + { + options.ClientId = settings.Value.Authentication.Google.ClientId; + options.ClientSecret = settings.Value.Authentication.Google.ClientSecret; + options.CallbackPath = "/api/signin-google"; // rmb to resister in the Google oauth dashboard + options.SignInScheme = IdentityConstants.ExternalScheme; // important to default to external scheme - https://stackoverflow.com/a/78674926/6514532 + + // Enable refresh token + options.SaveTokens = true; + options.AccessType = "offline"; + + // For debugging purpose + options.Events.OnRedirectToAuthorizationEndpoint = (context) => + { + logger.LogInformation( + "Request Path: {Request}", + context.Request.RootUri().AbsoluteUri + ); + context.HttpContext.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + } + ); + +builder + .Services.AddAuthorizationBuilder() + .AddPolicy( + SameAccountRequirement.PolicyName, + configurePolicy: (policy) => policy.Requirements.Add(new SameAccountRequirement()) + ); // Add razor pages support to render Blazor files -builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); +builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddExceptionHandler(); WebApplication app = builder.Build(); -using (IServiceScope serviceScope = app.Services.CreateScope()) { +using (IServiceScope serviceScope = app.Services.CreateScope()) +{ AppDbContext dbContext = serviceScope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(); } // Use the global exception handler -app.UseExceptionHandler((_) => {}); +app.UseExceptionHandler((_) => { }); // To serve the Svelte SPA files app.UseFileServer(); // needed for Blazor app.UseAntiforgery(); -app.MapRazorComponents() - .AddInteractiveServerRenderMode(); +app.MapRazorComponents().AddInteractiveServerRenderMode(); // Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) { +if (app.Environment.IsDevelopment()) +{ app.MapOpenApi(); } @@ -149,4 +168,4 @@ // Needed for unit testing // ReSharper disable once ClassNeverInstantiated.Global -public partial class Program; \ No newline at end of file +public partial class Program; From 7b1959dcc55739109be0550068d90700f3f60202 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:11:26 +0800 Subject: [PATCH 04/10] chore: update --- src/evently.client/src/routes/gatherings/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index bfeab0b..1671361 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -11,7 +11,12 @@ import { Icon } from "@iconify/react/dist/offline"; export const Route = createFileRoute("/gatherings/")({ component: GatheringsPage, loader: async () => { - const categories: Category[] = await getCategories(); + let categories: Category[] = []; + try { + categories = await getCategories(); + } catch (error) { + console.error(error); + } return { categories }; }, pendingComponent: () => ( From 364d26fe9c0ea23855d5bc0ae54210f54f3f14dc Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:12:51 +0800 Subject: [PATCH 05/10] chore: update --- src/evently.client/src/routes/gatherings/index.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index 1671361..bd87e3a 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -12,10 +12,15 @@ export const Route = createFileRoute("/gatherings/")({ component: GatheringsPage, loader: async () => { let categories: Category[] = []; - try { - categories = await getCategories(); - } catch (error) { - console.error(error); + let attempts = 2; + while (attempts > 0) { + try { + categories = await getCategories(); + break; + } catch (error) { + attempts -= 1; + console.error(error); + } } return { categories }; }, From 8b3e481e1b575bab963554ffd7e70ee828a0d322 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:16:46 +0800 Subject: [PATCH 06/10] chore: update --- .editorconfig | 355 ---- .../Common/Data/AppDbContext.cs | 1112 +++++------ .../Migrations/20250913035915_SQLServer.cs | 1714 ++++++++--------- .../20250927053802_UpdateSeededDates.cs | 1190 ++++++------ .../Common/Extensions/LoggerExtension.cs | 72 +- .../Common/Extensions/MapperExtension.cs | 122 +- .../Extensions/ServiceContainerExtensions.cs | 30 +- .../Common/Extensions/UtilsExtension.cs | 32 +- .../Middlewares/GlobalExceptionHandler.cs | 82 +- .../Domains/Entities/Account.cs | 6 +- .../Domains/Entities/Booking.cs | 42 +- .../Domains/Entities/Category.cs | 12 +- .../Domains/Entities/Gathering.cs | 40 +- .../Entities/GatheringCategoryDetail.cs | 8 +- .../ExternalLoginProviderException.cs | 2 +- .../Domains/Interfaces/IAccountsService.cs | 4 +- .../Domains/Interfaces/IBookingService.cs | 34 +- .../Domains/Interfaces/ICategoryService.cs | 4 +- .../Domains/Interfaces/IEmailerAdapter.cs | 4 +- .../Domains/Interfaces/IGatheringService.cs | 34 +- .../Domains/Interfaces/IMediaRenderer.cs | 18 +- .../Interfaces/IObjectStorageService.cs | 24 +- .../Domains/Models/BookingReqDto.cs | 14 +- .../Domains/Models/GatheringReqDto.cs | 20 +- .../Domains/Models/PageResult.cs | 4 +- src/Evently.Server/Domains/Models/Settings.cs | 32 +- .../Accounts/Controllers/AccountController.cs | 184 +- .../Services/AccountAuthorizationHandler.cs | 54 +- .../Accounts/Services/AccountExtensions.cs | 44 +- .../Accounts/Services/AccountService.cs | 124 +- .../Accounts/Services/AccountValidator.cs | 38 +- .../Controllers/BookingsController.cs | 212 +- .../Bookings/Services/BookingService.cs | 310 +-- .../Bookings/Services/BookingValidator.cs | 14 +- .../Controllers/CategoriesController.cs | 38 +- .../Categories/Services/CategoryService.cs | 44 +- .../Features/Emails/Services/EmailAdapter.cs | 88 +- .../Emails/Services/EmailBackgroundService.cs | 70 +- .../Features/Emails/Services/MediaRenderer.cs | 54 +- .../Files/Controllers/FilesController.cs | 80 +- .../Files/Services/ObjectStorageService.cs | 256 +-- .../Controllers/GatheringsController.cs | 296 +-- .../Gatherings/Services/GatheringService.cs | 216 +-- .../Gatherings/Services/GatheringValidator.cs | 56 +- .../Controllers/HealthChecksController.cs | 44 +- src/Evently.Server/Program.cs | 102 +- 46 files changed, 3490 insertions(+), 3845 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 2ae896e..0000000 --- a/.editorconfig +++ /dev/null @@ -1,355 +0,0 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories -root = true - -# C# files -[*.cs] - -#### Core EditorConfig Options #### - -# Indentation and spacing -indent_size = 4 -indent_style = tab -tab_width = 4 - -# New line preferences -end_of_line = crlf -insert_final_newline = false - -#### .NET Coding Conventions #### - -# Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset - -# this. and Me. preferences -dotnet_style_qualification_for_event = false -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false -dotnet_style_qualification_for_property = false - -# Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true -dotnet_style_predefined_type_for_member_access = true - -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_operators = never_if_unnecessary -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity - -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members - -# Expression-level preferences -dotnet_style_coalesce_expression = true -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true -dotnet_style_prefer_collection_expression = when_types_loosely_match -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true -dotnet_style_prefer_conditional_expression_over_return = true -dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true -dotnet_style_prefer_simplified_boolean_expressions = true -dotnet_style_prefer_simplified_interpolation = true - -# Seal internal types -dotnet_diagnostic.CA1852.severity = warning - -# Field preferences -dotnet_style_readonly_field = true - -# Parameter preferences -dotnet_code_quality_unused_parameters = all - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none - -# New line preferences -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = true - -#### C# Coding Conventions #### - -# var preferences -csharp_style_var_elsewhere = false:silent -csharp_style_var_for_built_in_types = false:suggestion -csharp_style_var_when_type_is_apparent = false:silent - -# Expression-bodied members -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent - -# Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_prefer_extended_property_pattern = true:suggestion -csharp_style_prefer_not_pattern = true:suggestion -csharp_style_prefer_pattern_matching = true:suggestion -csharp_style_prefer_switch_expression = true:suggestion - -# Null-checking preferences -csharp_style_conditional_delegate_call = true:suggestion - -# Modifier preferences -csharp_prefer_static_anonymous_function = true:suggestion -csharp_prefer_static_local_function = true:suggestion -csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async -csharp_style_prefer_readonly_struct = true:suggestion -csharp_style_prefer_readonly_struct_member = true:suggestion - -# Code-block preferences -csharp_prefer_braces = true:suggestion -csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = file_scoped:warning -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_primary_constructors = true:suggestion -csharp_style_prefer_top_level_statements = true:silent - -# Expression-level preferences -csharp_prefer_simple_default_expression = true:suggestion -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_prefer_index_operator = true:suggestion -csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_range_operator = true:suggestion -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_throw_expression = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:silent - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:suggestion - -# New line preferences -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent -csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent -csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent - -#### C# Formatting Rules #### - -# New line preferences -csharp_new_line_before_catch = false -csharp_new_line_before_else = false -csharp_new_line_before_finally = false -csharp_new_line_before_members_in_anonymous_types = false -csharp_new_line_before_members_in_object_initializers = false -csharp_new_line_before_open_brace = none -csharp_new_line_between_query_expression_clauses = false - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# Space preferences -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# Wrapping preferences -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### Naming styles #### - -# Naming rules - -dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion -dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface -dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i - -dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.types_should_be_pascal_case.symbols = types -dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case - -# Symbol specifications - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = - -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = -dotnet_naming_style.pascal_case.capitalization = pascal_case - -dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = -dotnet_naming_style.begins_with_i.capitalization = pascal_case -csharp_prefer_system_threading_lock = true:suggestion - -[*.{cs,vb}] -dotnet_style_operator_placement_when_wrapping = beginning_of_line -tab_width = 4 -indent_size = 4 -end_of_line = crlf -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_auto_properties = true:silent -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:silent -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_simplified_interpolation = true:suggestion -dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion -dotnet_style_namespace_match_folder = true:suggestion -dotnet_style_readonly_field = true:suggestion -dotnet_style_predefined_type_for_locals_parameters_members = true:silent -dotnet_style_predefined_type_for_member_access = true:silent -dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent -dotnet_style_allow_multiple_blank_lines_experimental = true:silent -dotnet_style_allow_statement_immediately_after_block_experimental = true:silent -dotnet_code_quality_unused_parameters = all:suggestion -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent -dotnet_style_qualification_for_field = false:silent -dotnet_style_qualification_for_property = true:silent -dotnet_style_qualification_for_method = false:silent -dotnet_style_qualification_for_event = false:silent -dotnet_style_lambda_parameter_parentheses = always - -[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] - -# Visual C++ Code Style settings - -cpp_generate_documentation_comments = xml - -# Visual C++ Formatting settings - -cpp_indent_braces = false -cpp_indent_multi_line_relative_to = innermost_parenthesis -cpp_indent_within_parentheses = indent -cpp_indent_preserve_within_parentheses = true -cpp_indent_case_contents = true -cpp_indent_case_labels = false -cpp_indent_case_contents_when_block = false -cpp_indent_lambda_braces_when_parameter = true -cpp_indent_goto_labels = one_left -cpp_indent_preprocessor = leftmost_column -cpp_indent_access_specifiers = false -cpp_indent_namespace_contents = true -cpp_indent_preserve_comments = false -cpp_new_line_before_open_brace_namespace = ignore -cpp_new_line_before_open_brace_type = ignore -cpp_new_line_before_open_brace_function = ignore -cpp_new_line_before_open_brace_block = ignore -cpp_new_line_before_open_brace_lambda = ignore -cpp_new_line_scope_braces_on_separate_lines = false -cpp_new_line_close_brace_same_line_empty_type = false -cpp_new_line_close_brace_same_line_empty_function = false -cpp_new_line_before_catch = true -cpp_new_line_before_else = true -cpp_new_line_before_while_in_do_while = false -cpp_space_before_function_open_parenthesis = remove -cpp_space_within_parameter_list_parentheses = false -cpp_space_between_empty_parameter_list_parentheses = false -cpp_space_after_keywords_in_control_flow_statements = true -cpp_space_within_control_flow_statement_parentheses = false -cpp_space_before_lambda_open_parenthesis = false -cpp_space_within_cast_parentheses = false -cpp_space_after_cast_close_parenthesis = false -cpp_space_within_expression_parentheses = false -cpp_space_before_block_open_brace = true -cpp_space_between_empty_braces = false -cpp_space_before_initializer_list_open_brace = false -cpp_space_within_initializer_list_braces = true -cpp_space_preserve_in_initializer_list = true -cpp_space_before_open_square_bracket = false -cpp_space_within_square_brackets = false -cpp_space_before_empty_square_brackets = false -cpp_space_between_empty_square_brackets = false -cpp_space_group_square_brackets = true -cpp_space_within_lambda_brackets = false -cpp_space_between_empty_lambda_brackets = false -cpp_space_before_comma = false -cpp_space_after_comma = true -cpp_space_remove_around_member_operators = true -cpp_space_before_inheritance_colon = true -cpp_space_before_constructor_colon = true -cpp_space_remove_before_semicolon = true -cpp_space_after_semicolon = true -cpp_space_remove_around_unary_operator = true -cpp_space_around_binary_operator = insert -cpp_space_around_assignment_operator = insert -cpp_space_pointer_reference_alignment = left -cpp_space_around_ternary_operator = insert -cpp_use_unreal_engine_macro_formatting = true -cpp_wrap_preserve_blocks = one_liners - -# Visual C++ Inlcude Cleanup settings - -cpp_include_cleanup_add_missing_error_tag_type = suggestion -cpp_include_cleanup_remove_unused_error_tag_type = dimmed -cpp_include_cleanup_optimize_unused_error_tag_type = suggestion -cpp_include_cleanup_sort_after_edits = false -cpp_sort_includes_error_tag_type = none -cpp_sort_includes_priority_case_sensitive = false -cpp_sort_includes_priority_style = quoted -cpp_includes_style = default -cpp_includes_use_forward_slash = true - diff --git a/src/Evently.Server/Common/Data/AppDbContext.cs b/src/Evently.Server/Common/Data/AppDbContext.cs index bc55bf6..e595566 100644 --- a/src/Evently.Server/Common/Data/AppDbContext.cs +++ b/src/Evently.Server/Common/Data/AppDbContext.cs @@ -8,575 +8,575 @@ namespace Evently.Server.Common.Data; public class AppDbContext(DbContextOptions options) - : IdentityDbContext(options) + : IdentityDbContext(options) { - public DbSet Accounts { get; set; } - public DbSet Bookings { get; set; } - public DbSet Gatherings { get; set; } - public DbSet GatheringCategoryDetails { get; set; } - public DbSet Categories { get; set; } + public DbSet Accounts { get; set; } + public DbSet Bookings { get; set; } + public DbSet Gatherings { get; set; } + public DbSet GatheringCategoryDetails { get; set; } + public DbSet Categories { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); - // for unit testing, sqlite is used - if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") - { - ConfigureSqlite(modelBuilder); - } + // for unit testing, sqlite is used + if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite") + { + ConfigureSqlite(modelBuilder); + } - // Postgres identity configuration - modelBuilder - .Entity() - .Property(g => g.GatheringId) - .HasIdentityOptions(startValue: 20); + // Postgres identity configuration + modelBuilder + .Entity() + .Property(g => g.GatheringId) + .HasIdentityOptions(startValue: 20); - modelBuilder - .Entity() - .Property(c => c.CategoryId) - .HasIdentityOptions(startValue: 20); + modelBuilder + .Entity() + .Property(c => c.CategoryId) + .HasIdentityOptions(startValue: 20); - SeedData(modelBuilder); - } + SeedData(modelBuilder); + } - private static void SeedData(ModelBuilder modelBuilder) - { - // Fixed Account IDs for referencing - const string hostUserId = "empty-user-12345"; - const string guestUserId = "guest-user-22222"; + private static void SeedData(ModelBuilder modelBuilder) + { + // Fixed Account IDs for referencing + const string hostUserId = "empty-user-12345"; + const string guestUserId = "guest-user-22222"; - // Seed Accounts (without proper password hashes - they won't be able to login) - modelBuilder - .Entity() - .HasData( - new Account - { - Id = hostUserId, // Fixed constant ID - UserName = "empty_user", - NormalizedUserName = "EMPTY_USER", - Email = "empty@example.com", - NormalizedEmail = "EMPTY@EXAMPLE.COM", - Name = "Empty User", - EmailConfirmed = false, - PasswordHash = null, // No password - unusable account - SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant - ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant - PhoneNumber = null, - PhoneNumberConfirmed = false, - TwoFactorEnabled = false, - LockoutEnabled = true, - AccessFailedCount = 0, - }, - new Account - { - Id = guestUserId, // Fixed constant ID - UserName = "guest_user2", - NormalizedUserName = "GUEST_USER_2", - Email = "guest@example.com", - NormalizedEmail = "GUEST@EXAMPLE.COM", - Name = "Guest User", - EmailConfirmed = false, - PasswordHash = null, // No password - unusable account - SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant - ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant - PhoneNumber = null, - PhoneNumberConfirmed = false, - TwoFactorEnabled = false, - LockoutEnabled = true, - AccessFailedCount = 0, - } - ); + // Seed Accounts (without proper password hashes - they won't be able to login) + modelBuilder + .Entity() + .HasData( + new Account + { + Id = hostUserId, // Fixed constant ID + UserName = "empty_user", + NormalizedUserName = "EMPTY_USER", + Email = "empty@example.com", + NormalizedEmail = "EMPTY@EXAMPLE.COM", + Name = "Empty User", + EmailConfirmed = false, + PasswordHash = null, // No password - unusable account + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant + PhoneNumber = null, + PhoneNumberConfirmed = false, + TwoFactorEnabled = false, + LockoutEnabled = true, + AccessFailedCount = 0, + }, + new Account + { + Id = guestUserId, // Fixed constant ID + UserName = "guest_user2", + NormalizedUserName = "GUEST_USER_2", + Email = "guest@example.com", + NormalizedEmail = "GUEST@EXAMPLE.COM", + Name = "Guest User", + EmailConfirmed = false, + PasswordHash = null, // No password - unusable account + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", // Fixed constant + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", // Fixed constant + PhoneNumber = null, + PhoneNumberConfirmed = false, + TwoFactorEnabled = false, + LockoutEnabled = true, + AccessFailedCount = 0, + } + ); - // Seed Categories - modelBuilder - .Entity() - .HasData( - new Category { CategoryId = 1, CategoryName = "Information Technology" }, - new Category { CategoryId = 2, CategoryName = "Business & Networking" }, - new Category { CategoryId = 3, CategoryName = "Arts & Culture" } - ); + // Seed Categories + modelBuilder + .Entity() + .HasData( + new Category { CategoryId = 1, CategoryName = "Information Technology" }, + new Category { CategoryId = 2, CategoryName = "Business & Networking" }, + new Category { CategoryId = 3, CategoryName = "Arts & Culture" } + ); - // Singapore timezone offset - TimeSpan singaporeOffset = TimeSpan.Zero; + // Singapore timezone offset + TimeSpan singaporeOffset = TimeSpan.Zero; - // Seed Gatherings - modelBuilder - .Entity() - .HasData( - new Gathering - { - GatheringId = 1, - Name = "Tech Innovation Summit", - Description = - "A comprehensive summit exploring the latest in AI and machine learning", - Location = "Marina Bay Sands Convention Centre, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 5, - hour: 9, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 5, - hour: 17, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 2, - Name = "Startup Networking Night", - Description = "Connect with fellow entrepreneurs and investors", - Location = "Clarke Quay Central, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 10, - hour: 18, - minute: 30, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 10, - hour: 22, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 3, - Name = "Digital Art Exhibition", - Description = "Showcasing contemporary digital art from emerging artists", - Location = "National Gallery Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 15, - hour: 10, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 15, - hour: 18, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 4, - Name = "Web Development Workshop", - Description = "Learn modern web development techniques and best practices", - Location = "Singapore Science Centre", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 8, - hour: 13, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 8, - hour: 17, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 5, - Name = "Business Strategy Seminar", - Description = "Advanced strategies for scaling your business", - Location = "Raffles City Convention Centre, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 20, - hour: 14, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 20, - hour: 16, - minute: 30, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 6, - Name = "Photography Masterclass", - Description = "Professional photography techniques and portfolio building", - Location = "Gardens by the Bay, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 22, - hour: 8, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 22, - hour: 12, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 7, - Name = "Mobile App Development Bootcamp", - Description = "Intensive bootcamp covering iOS and Android development", - Location = "NUS School of Computing, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 12, - hour: 9, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 12, - hour: 18, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 8, - Name = "Investment & Finance Forum", - Description = "Learn about personal finance and investment strategies", - Location = "Suntec Singapore Convention Centre", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 25, - hour: 14, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 25, - hour: 17, - minute: 30, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 9, - Name = "Creative Writing Workshop", - Description = "Explore storytelling techniques and creative expression", - Location = "Esplanade Theatres, Singapore", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 28, - hour: 10, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 28, - hour: 15, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 10, - Name = "Cloud Computing Conference", - Description = "Latest trends in cloud architecture and DevOps", - Location = "Singapore EXPO", - Start = new DateTimeOffset( - year: 2026, - month: 12, - day: 30, - hour: 9, - minute: 30, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2026, - month: 12, - day: 30, - hour: 17, - minute: 30, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 11, - Name = "E-commerce Mastery", - Description = "Build and scale your online business effectively", - Location = "Marina Bay Financial Centre, Singapore", - Start = new DateTimeOffset( - year: 2027, - month: 1, - day: 3, - hour: 13, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2027, - month: 1, - day: 3, - hour: 18, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 12, - Name = "Contemporary Dance Performance", - Description = "An evening of modern dance and artistic expression", - Location = "Victoria Theatre, Singapore", - Start = new DateTimeOffset( - year: 2027, - month: 1, - day: 5, - hour: 19, - minute: 30, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2027, - month: 1, - day: 5, - hour: 22, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 13, - Name = "Cybersecurity Awareness Training", - Description = "Essential cybersecurity practices for businesses", - Location = "Singapore Management University", - Start = new DateTimeOffset( - year: 2027, - month: 1, - day: 8, - hour: 10, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2027, - month: 1, - day: 8, - hour: 16, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - }, - new Gathering - { - GatheringId = 14, - Name = "Leadership Excellence Workshop", - Description = "Develop essential leadership skills for modern managers", - Location = "Orchard Hotel Singapore", - Start = new DateTimeOffset( - year: 2027, - month: 1, - day: 10, - hour: 9, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2027, - month: 1, - day: 10, - hour: 17, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = hostUserId, - }, - new Gathering - { - GatheringId = 15, - Name = "Film & Media Production Showcase", - Description = "Independent filmmakers present their latest works", - Location = "Singapore International Film Festival Venue", - Start = new DateTimeOffset( - year: 2027, - month: 1, - day: 12, - hour: 18, - minute: 0, - second: 0, - singaporeOffset - ), - End = new DateTimeOffset( - year: 2027, - month: 1, - day: 12, - hour: 23, - minute: 0, - second: 0, - singaporeOffset - ), - OrganiserId = guestUserId, - } - ); + // Seed Gatherings + modelBuilder + .Entity() + .HasData( + new Gathering + { + GatheringId = 1, + Name = "Tech Innovation Summit", + Description = + "A comprehensive summit exploring the latest in AI and machine learning", + Location = "Marina Bay Sands Convention Centre, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 5, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 5, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 2, + Name = "Startup Networking Night", + Description = "Connect with fellow entrepreneurs and investors", + Location = "Clarke Quay Central, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 10, + hour: 18, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 10, + hour: 22, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 3, + Name = "Digital Art Exhibition", + Description = "Showcasing contemporary digital art from emerging artists", + Location = "National Gallery Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 15, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 15, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 4, + Name = "Web Development Workshop", + Description = "Learn modern web development techniques and best practices", + Location = "Singapore Science Centre", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 8, + hour: 13, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 8, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 5, + Name = "Business Strategy Seminar", + Description = "Advanced strategies for scaling your business", + Location = "Raffles City Convention Centre, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 20, + hour: 14, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 20, + hour: 16, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 6, + Name = "Photography Masterclass", + Description = "Professional photography techniques and portfolio building", + Location = "Gardens by the Bay, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 22, + hour: 8, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 22, + hour: 12, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 7, + Name = "Mobile App Development Bootcamp", + Description = "Intensive bootcamp covering iOS and Android development", + Location = "NUS School of Computing, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 12, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 12, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 8, + Name = "Investment & Finance Forum", + Description = "Learn about personal finance and investment strategies", + Location = "Suntec Singapore Convention Centre", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 25, + hour: 14, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 25, + hour: 17, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 9, + Name = "Creative Writing Workshop", + Description = "Explore storytelling techniques and creative expression", + Location = "Esplanade Theatres, Singapore", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 28, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 28, + hour: 15, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 10, + Name = "Cloud Computing Conference", + Description = "Latest trends in cloud architecture and DevOps", + Location = "Singapore EXPO", + Start = new DateTimeOffset( + year: 2026, + month: 12, + day: 30, + hour: 9, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2026, + month: 12, + day: 30, + hour: 17, + minute: 30, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 11, + Name = "E-commerce Mastery", + Description = "Build and scale your online business effectively", + Location = "Marina Bay Financial Centre, Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 3, + hour: 13, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 3, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 12, + Name = "Contemporary Dance Performance", + Description = "An evening of modern dance and artistic expression", + Location = "Victoria Theatre, Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 5, + hour: 19, + minute: 30, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 5, + hour: 22, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 13, + Name = "Cybersecurity Awareness Training", + Description = "Essential cybersecurity practices for businesses", + Location = "Singapore Management University", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 8, + hour: 10, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 8, + hour: 16, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + }, + new Gathering + { + GatheringId = 14, + Name = "Leadership Excellence Workshop", + Description = "Develop essential leadership skills for modern managers", + Location = "Orchard Hotel Singapore", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 10, + hour: 9, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 10, + hour: 17, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = hostUserId, + }, + new Gathering + { + GatheringId = 15, + Name = "Film & Media Production Showcase", + Description = "Independent filmmakers present their latest works", + Location = "Singapore International Film Festival Venue", + Start = new DateTimeOffset( + year: 2027, + month: 1, + day: 12, + hour: 18, + minute: 0, + second: 0, + singaporeOffset + ), + End = new DateTimeOffset( + year: 2027, + month: 1, + day: 12, + hour: 23, + minute: 0, + second: 0, + singaporeOffset + ), + OrganiserId = guestUserId, + } + ); - // Seed GatheringCategoryDetails - modelBuilder - .Entity() - .HasData( - new GatheringCategoryDetail { GatheringId = 1, CategoryId = 1 }, // Tech Innovation Summit -> IT - new GatheringCategoryDetail { GatheringId = 2, CategoryId = 2 }, // Startup Networking Night -> Business - new GatheringCategoryDetail { GatheringId = 3, CategoryId = 3 }, // Digital Art Exhibition -> Arts - new GatheringCategoryDetail { GatheringId = 4, CategoryId = 1 }, // Web Development Workshop -> IT - new GatheringCategoryDetail { GatheringId = 5, CategoryId = 2 }, // Business Strategy Seminar -> Business - new GatheringCategoryDetail { GatheringId = 6, CategoryId = 3 }, // Photography Masterclass -> Arts - new GatheringCategoryDetail { GatheringId = 7, CategoryId = 1 }, // Mobile App Development Bootcamp -> IT - new GatheringCategoryDetail { GatheringId = 8, CategoryId = 2 }, // Investment & Finance Forum -> Business - new GatheringCategoryDetail { GatheringId = 9, CategoryId = 3 }, // Creative Writing Workshop -> Arts - new GatheringCategoryDetail { GatheringId = 10, CategoryId = 1 }, // Cloud Computing Conference -> IT - new GatheringCategoryDetail { GatheringId = 11, CategoryId = 2 }, // E-commerce Mastery -> Business - new GatheringCategoryDetail { GatheringId = 12, CategoryId = 3 }, // Contemporary Dance Performance -> Arts - new GatheringCategoryDetail { GatheringId = 13, CategoryId = 1 }, // Cybersecurity Awareness Training -> IT - new GatheringCategoryDetail { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business - new GatheringCategoryDetail { GatheringId = 15, CategoryId = 3 } // Film & Media Production Showcase -> Arts - ); + // Seed GatheringCategoryDetails + modelBuilder + .Entity() + .HasData( + new GatheringCategoryDetail { GatheringId = 1, CategoryId = 1 }, // Tech Innovation Summit -> IT + new GatheringCategoryDetail { GatheringId = 2, CategoryId = 2 }, // Startup Networking Night -> Business + new GatheringCategoryDetail { GatheringId = 3, CategoryId = 3 }, // Digital Art Exhibition -> Arts + new GatheringCategoryDetail { GatheringId = 4, CategoryId = 1 }, // Web Development Workshop -> IT + new GatheringCategoryDetail { GatheringId = 5, CategoryId = 2 }, // Business Strategy Seminar -> Business + new GatheringCategoryDetail { GatheringId = 6, CategoryId = 3 }, // Photography Masterclass -> Arts + new GatheringCategoryDetail { GatheringId = 7, CategoryId = 1 }, // Mobile App Development Bootcamp -> IT + new GatheringCategoryDetail { GatheringId = 8, CategoryId = 2 }, // Investment & Finance Forum -> Business + new GatheringCategoryDetail { GatheringId = 9, CategoryId = 3 }, // Creative Writing Workshop -> Arts + new GatheringCategoryDetail { GatheringId = 10, CategoryId = 1 }, // Cloud Computing Conference -> IT + new GatheringCategoryDetail { GatheringId = 11, CategoryId = 2 }, // E-commerce Mastery -> Business + new GatheringCategoryDetail { GatheringId = 12, CategoryId = 3 }, // Contemporary Dance Performance -> Arts + new GatheringCategoryDetail { GatheringId = 13, CategoryId = 1 }, // Cybersecurity Awareness Training -> IT + new GatheringCategoryDetail { GatheringId = 14, CategoryId = 2 }, // Leadership Excellence Workshop -> Business + new GatheringCategoryDetail { GatheringId = 15, CategoryId = 3 } // Film & Media Production Showcase -> Arts + ); - // Seed Bookings with fixed DateTimeOffset values - // Fixed DateTimeOffset for seeding (static value) - DateTimeOffset fixedCreationTime = new( - year: 2025, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - TimeSpan.Zero - ); + // Seed Bookings with fixed DateTimeOffset values + // Fixed DateTimeOffset for seeding (static value) + DateTimeOffset fixedCreationTime = new( + year: 2025, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + TimeSpan.Zero + ); - modelBuilder - .Entity() - .HasData( - new Booking - { - BookingId = "book_abc123456", - GatheringId = 1, - AttendeeId = guestUserId, - CreationDateTime = fixedCreationTime, - CheckInDateTime = null, - CheckoutDateTime = null, - CancellationDateTime = null, - }, - new Booking - { - BookingId = "book_def789012", - GatheringId = 2, - AttendeeId = hostUserId, - CreationDateTime = fixedCreationTime.AddHours(1), // Slightly different time - CheckInDateTime = null, - CheckoutDateTime = null, - CancellationDateTime = null, - } - ); - } + modelBuilder + .Entity() + .HasData( + new Booking + { + BookingId = "book_abc123456", + GatheringId = 1, + AttendeeId = guestUserId, + CreationDateTime = fixedCreationTime, + CheckInDateTime = null, + CheckoutDateTime = null, + CancellationDateTime = null, + }, + new Booking + { + BookingId = "book_def789012", + GatheringId = 2, + AttendeeId = hostUserId, + CreationDateTime = fixedCreationTime.AddHours(1), // Slightly different time + CheckInDateTime = null, + CheckoutDateTime = null, + CancellationDateTime = null, + } + ); + } - // https://stackoverflow.com/a/76152994/6514532 - private static void ConfigureSqlite(ModelBuilder modelBuilder) - { - // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, - // see the limitations here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations. - // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754. - foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) - { - IEnumerable properties = entityType - .ClrType.GetProperties() - .Where(p => - p.PropertyType == typeof(DateTimeOffset) - || p.PropertyType == typeof(DateTimeOffset?) - ); - foreach (PropertyInfo property in properties) - { - modelBuilder - .Entity(entityType.Name) - .Property(property.Name) - .HasConversion(new DateTimeOffsetToBinaryConverter()); - } - } - } + // https://stackoverflow.com/a/76152994/6514532 + private static void ConfigureSqlite(ModelBuilder modelBuilder) + { + // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, + // see the limitations here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations. + // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754. + foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + { + IEnumerable properties = entityType + .ClrType.GetProperties() + .Where(p => + p.PropertyType == typeof(DateTimeOffset) + || p.PropertyType == typeof(DateTimeOffset?) + ); + foreach (PropertyInfo property in properties) + { + modelBuilder + .Entity(entityType.Name) + .Property(property.Name) + .HasConversion(new DateTimeOffsetToBinaryConverter()); + } + } + } } diff --git a/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs index 58f5dc2..94cd494 100644 --- a/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs @@ -7,861 +7,861 @@ namespace Evently.Server.Common.Data.Migrations { - /// - public partial class SQLServer : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - NormalizedName = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column( - type: "nvarchar(100)", - maxLength: 100, - nullable: false - ), - UserName = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - NormalizedUserName = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - Email = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - NormalizedEmail = table.Column( - type: "nvarchar(256)", - maxLength: 256, - nullable: true - ), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column( - type: "datetimeoffset", - nullable: true - ), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false), - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - } - ); - - migrationBuilder.CreateTable( - name: "Categories", - columns: table => new - { - CategoryId = table - .Column(type: "bigint", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - CategoryName = table.Column( - type: "nvarchar(100)", - maxLength: 100, - nullable: false - ), - Approved = table.Column(type: "bit", nullable: false), - }, - constraints: table => - { - table.PrimaryKey("PK_Categories", x => x.CategoryId); - } - ); - - migrationBuilder.CreateTable( - name: "Gatherings", - columns: table => new - { - GatheringId = table - .Column(type: "bigint", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column( - type: "nvarchar(100)", - maxLength: 100, - nullable: false - ), - Description = table.Column( - type: "nvarchar(max)", - maxLength: 10000, - nullable: false - ), - Start = table.Column(type: "datetimeoffset", nullable: false), - End = table.Column(type: "datetimeoffset", nullable: false), - Location = table.Column( - type: "nvarchar(100)", - maxLength: 100, - nullable: false - ), - CoverSrc = table.Column( - type: "nvarchar(1000)", - maxLength: 1000, - nullable: true - ), - OrganiserId = table.Column( - type: "nvarchar(100)", - maxLength: 100, - nullable: false - ), - CancellationDateTime = table.Column( - type: "datetimeoffset", - nullable: true - ), - }, - constraints: table => - { - table.PrimaryKey("PK_Gatherings", x => x.GatheringId); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table - .Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table - .Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column( - type: "nvarchar(max)", - nullable: true - ), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - }, - constraints: table => - { - table.PrimaryKey( - "PK_AspNetUserLogins", - x => new { x.LoginProvider, x.ProviderKey } - ); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true), - }, - constraints: table => - { - table.PrimaryKey( - "PK_AspNetUserTokens", - x => new - { - x.UserId, - x.LoginProvider, - x.Name, - } - ); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "Bookings", - columns: table => new - { - BookingId = table.Column( - type: "nvarchar(50)", - maxLength: 50, - nullable: false - ), - AttendeeId = table.Column(type: "nvarchar(450)", nullable: false), - GatheringId = table.Column(type: "bigint", nullable: false), - CreationDateTime = table.Column( - type: "datetimeoffset", - nullable: false - ), - CheckInDateTime = table.Column( - type: "datetimeoffset", - nullable: true - ), - CheckoutDateTime = table.Column( - type: "datetimeoffset", - nullable: true - ), - CancellationDateTime = table.Column( - type: "datetimeoffset", - nullable: true - ), - }, - constraints: table => - { - table.PrimaryKey("PK_Bookings", x => x.BookingId); - table.ForeignKey( - name: "FK_Bookings_AspNetUsers_AttendeeId", - column: x => x.AttendeeId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade - ); - table.ForeignKey( - name: "FK_Bookings_Gatherings_GatheringId", - column: x => x.GatheringId, - principalTable: "Gatherings", - principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.CreateTable( - name: "GatheringCategoryDetails", - columns: table => new - { - GatheringId = table.Column(type: "bigint", nullable: false), - CategoryId = table.Column(type: "bigint", nullable: false), - }, - constraints: table => - { - table.PrimaryKey( - "PK_GatheringCategoryDetails", - x => new { x.GatheringId, x.CategoryId } - ); - table.ForeignKey( - name: "FK_GatheringCategoryDetails_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "CategoryId", - onDelete: ReferentialAction.Cascade - ); - table.ForeignKey( - name: "FK_GatheringCategoryDetails_Gatherings_GatheringId", - column: x => x.GatheringId, - principalTable: "Gatherings", - principalColumn: "GatheringId", - onDelete: ReferentialAction.Cascade - ); - } - ); - - migrationBuilder.InsertData( - table: "AspNetUsers", - columns: new[] - { - "Id", - "AccessFailedCount", - "ConcurrencyStamp", - "Email", - "EmailConfirmed", - "LockoutEnabled", - "LockoutEnd", - "Name", - "NormalizedEmail", - "NormalizedUserName", - "PasswordHash", - "PhoneNumber", - "PhoneNumberConfirmed", - "SecurityStamp", - "TwoFactorEnabled", - "UserName", - }, - values: new object[,] - { - { - "empty-user-12345", - 0, - "EMPTY-CONCURRENCY-STAMP-12345", - "empty@example.com", - false, - true, - null, - "Empty User", - "EMPTY@EXAMPLE.COM", - "EMPTY_USER", - null, - null, - false, - "EMPTY-SECURITY-STAMP-12345", - false, - "empty_user", - }, - { - "guest-user-22222", - 0, - "EMPTY-CONCURRENCY-STAMP-12345", - "guest@example.com", - false, - true, - null, - "Guest User", - "GUEST@EXAMPLE.COM", - "GUEST_USER_2", - null, - null, - false, - "EMPTY-SECURITY-STAMP-12345", - false, - "guest_user2", - }, - } - ); - - migrationBuilder.InsertData( - table: "Categories", - columns: new[] { "CategoryId", "Approved", "CategoryName" }, - values: new object[,] - { - { 1L, false, "Information Technology" }, - { 2L, false, "Business & Networking" }, - { 3L, false, "Arts & Culture" }, - } - ); - - migrationBuilder.InsertData( - table: "Gatherings", - columns: new[] - { - "GatheringId", - "CancellationDateTime", - "CoverSrc", - "Description", - "End", - "Location", - "Name", - "OrganiserId", - "Start", - }, - values: new object[,] - { - { - 1L, - null, - "", - "A comprehensive summit exploring the latest in AI and machine learning", - new DateTimeOffset( - new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Marina Bay Sands Convention Centre, Singapore", - "Tech Innovation Summit", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 2L, - null, - "", - "Connect with fellow entrepreneurs and investors", - new DateTimeOffset( - new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Clarke Quay Central, Singapore", - "Startup Networking Night", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 3L, - null, - "", - "Showcasing contemporary digital art from emerging artists", - new DateTimeOffset( - new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "National Gallery Singapore", - "Digital Art Exhibition", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 4L, - null, - "", - "Learn modern web development techniques and best practices", - new DateTimeOffset( - new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Singapore Science Centre", - "Web Development Workshop", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 5L, - null, - "", - "Advanced strategies for scaling your business", - new DateTimeOffset( - new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Raffles City Convention Centre, Singapore", - "Business Strategy Seminar", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 6L, - null, - "", - "Professional photography techniques and portfolio building", - new DateTimeOffset( - new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Gardens by the Bay, Singapore", - "Photography Masterclass", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 7L, - null, - "", - "Intensive bootcamp covering iOS and Android development", - new DateTimeOffset( - new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "NUS School of Computing, Singapore", - "Mobile App Development Bootcamp", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 8L, - null, - "", - "Learn about personal finance and investment strategies", - new DateTimeOffset( - new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Suntec Singapore Convention Centre", - "Investment & Finance Forum", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 9L, - null, - "", - "Explore storytelling techniques and creative expression", - new DateTimeOffset( - new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Esplanade Theatres, Singapore", - "Creative Writing Workshop", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 10L, - null, - "", - "Latest trends in cloud architecture and DevOps", - new DateTimeOffset( - new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Singapore EXPO", - "Cloud Computing Conference", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 11L, - null, - "", - "Build and scale your online business effectively", - new DateTimeOffset( - new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Marina Bay Financial Centre, Singapore", - "E-commerce Mastery", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 12L, - null, - "", - "An evening of modern dance and artistic expression", - new DateTimeOffset( - new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Victoria Theatre, Singapore", - "Contemporary Dance Performance", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 13L, - null, - "", - "Essential cybersecurity practices for businesses", - new DateTimeOffset( - new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Singapore Management University", - "Cybersecurity Awareness Training", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 14L, - null, - "", - "Develop essential leadership skills for modern managers", - new DateTimeOffset( - new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Orchard Hotel Singapore", - "Leadership Excellence Workshop", - "empty-user-12345", - new DateTimeOffset( - new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - { - 15L, - null, - "", - "Independent filmmakers present their latest works", - new DateTimeOffset( - new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - "Singapore International Film Festival Venue", - "Film & Media Production Showcase", - "guest-user-22222", - new DateTimeOffset( - new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - }, - } - ); - - migrationBuilder.InsertData( - table: "Bookings", - columns: new[] - { - "BookingId", - "AttendeeId", - "CancellationDateTime", - "CheckInDateTime", - "CheckoutDateTime", - "CreationDateTime", - "GatheringId", - }, - values: new object[,] - { - { - "book_abc123456", - "guest-user-22222", - null, - null, - null, - new DateTimeOffset( - new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - 1L, - }, - { - "book_def789012", - "empty-user-12345", - null, - null, - null, - new DateTimeOffset( - new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - 2L, - }, - } - ); - - migrationBuilder.InsertData( - table: "GatheringCategoryDetails", - columns: new[] { "CategoryId", "GatheringId" }, - values: new object[,] - { - { 1L, 1L }, - { 2L, 2L }, - { 3L, 3L }, - { 1L, 4L }, - { 2L, 5L }, - { 3L, 6L }, - { 1L, 7L }, - { 2L, 8L }, - { 3L, 9L }, - { 1L, 10L }, - { 2L, 11L }, - { 3L, 12L }, - { 1L, 13L }, - { 2L, 14L }, - { 3L, 15L }, - } - ); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId" - ); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL" - ); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId" - ); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId" - ); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId" - ); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail" - ); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL" - ); - - migrationBuilder.CreateIndex( - name: "IX_Bookings_AttendeeId", - table: "Bookings", - column: "AttendeeId" - ); - - migrationBuilder.CreateIndex( - name: "IX_Bookings_GatheringId", - table: "Bookings", - column: "GatheringId" - ); - - migrationBuilder.CreateIndex( - name: "IX_GatheringCategoryDetails_CategoryId", - table: "GatheringCategoryDetails", - column: "CategoryId" - ); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable(name: "AspNetRoleClaims"); - - migrationBuilder.DropTable(name: "AspNetUserClaims"); - - migrationBuilder.DropTable(name: "AspNetUserLogins"); - - migrationBuilder.DropTable(name: "AspNetUserRoles"); - - migrationBuilder.DropTable(name: "AspNetUserTokens"); - - migrationBuilder.DropTable(name: "Bookings"); - - migrationBuilder.DropTable(name: "GatheringCategoryDetails"); - - migrationBuilder.DropTable(name: "AspNetRoles"); - - migrationBuilder.DropTable(name: "AspNetUsers"); - - migrationBuilder.DropTable(name: "Categories"); - - migrationBuilder.DropTable(name: "Gatherings"); - } - } + /// + public partial class SQLServer : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + UserName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedUserName = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + Email = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + NormalizedEmail = table.Column( + type: "nvarchar(256)", + maxLength: 256, + nullable: true + ), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column( + type: "datetimeoffset", + nullable: true + ), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + } + ); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + CategoryId = table + .Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + CategoryName = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + Approved = table.Column(type: "bit", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.CategoryId); + } + ); + + migrationBuilder.CreateTable( + name: "Gatherings", + columns: table => new + { + GatheringId = table + .Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + Description = table.Column( + type: "nvarchar(max)", + maxLength: 10000, + nullable: false + ), + Start = table.Column(type: "datetimeoffset", nullable: false), + End = table.Column(type: "datetimeoffset", nullable: false), + Location = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + CoverSrc = table.Column( + type: "nvarchar(1000)", + maxLength: 1000, + nullable: true + ), + OrganiserId = table.Column( + type: "nvarchar(100)", + maxLength: 100, + nullable: false + ), + CancellationDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + }, + constraints: table => + { + table.PrimaryKey("PK_Gatherings", x => x.GatheringId); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table + .Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table + .Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column( + type: "nvarchar(max)", + nullable: true + ), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + }, + constraints: table => + { + table.PrimaryKey( + "PK_AspNetUserLogins", + x => new { x.LoginProvider, x.ProviderKey } + ); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true), + }, + constraints: table => + { + table.PrimaryKey( + "PK_AspNetUserTokens", + x => new + { + x.UserId, + x.LoginProvider, + x.Name, + } + ); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "Bookings", + columns: table => new + { + BookingId = table.Column( + type: "nvarchar(50)", + maxLength: 50, + nullable: false + ), + AttendeeId = table.Column(type: "nvarchar(450)", nullable: false), + GatheringId = table.Column(type: "bigint", nullable: false), + CreationDateTime = table.Column( + type: "datetimeoffset", + nullable: false + ), + CheckInDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + CheckoutDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + CancellationDateTime = table.Column( + type: "datetimeoffset", + nullable: true + ), + }, + constraints: table => + { + table.PrimaryKey("PK_Bookings", x => x.BookingId); + table.ForeignKey( + name: "FK_Bookings_AspNetUsers_AttendeeId", + column: x => x.AttendeeId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + table.ForeignKey( + name: "FK_Bookings_Gatherings_GatheringId", + column: x => x.GatheringId, + principalTable: "Gatherings", + principalColumn: "GatheringId", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateTable( + name: "GatheringCategoryDetails", + columns: table => new + { + GatheringId = table.Column(type: "bigint", nullable: false), + CategoryId = table.Column(type: "bigint", nullable: false), + }, + constraints: table => + { + table.PrimaryKey( + "PK_GatheringCategoryDetails", + x => new { x.GatheringId, x.CategoryId } + ); + table.ForeignKey( + name: "FK_GatheringCategoryDetails_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Cascade + ); + table.ForeignKey( + name: "FK_GatheringCategoryDetails_Gatherings_GatheringId", + column: x => x.GatheringId, + principalTable: "Gatherings", + principalColumn: "GatheringId", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.InsertData( + table: "AspNetUsers", + columns: new[] + { + "Id", + "AccessFailedCount", + "ConcurrencyStamp", + "Email", + "EmailConfirmed", + "LockoutEnabled", + "LockoutEnd", + "Name", + "NormalizedEmail", + "NormalizedUserName", + "PasswordHash", + "PhoneNumber", + "PhoneNumberConfirmed", + "SecurityStamp", + "TwoFactorEnabled", + "UserName", + }, + values: new object[,] + { + { + "empty-user-12345", + 0, + "EMPTY-CONCURRENCY-STAMP-12345", + "empty@example.com", + false, + true, + null, + "Empty User", + "EMPTY@EXAMPLE.COM", + "EMPTY_USER", + null, + null, + false, + "EMPTY-SECURITY-STAMP-12345", + false, + "empty_user", + }, + { + "guest-user-22222", + 0, + "EMPTY-CONCURRENCY-STAMP-12345", + "guest@example.com", + false, + true, + null, + "Guest User", + "GUEST@EXAMPLE.COM", + "GUEST_USER_2", + null, + null, + false, + "EMPTY-SECURITY-STAMP-12345", + false, + "guest_user2", + }, + } + ); + + migrationBuilder.InsertData( + table: "Categories", + columns: new[] { "CategoryId", "Approved", "CategoryName" }, + values: new object[,] + { + { 1L, false, "Information Technology" }, + { 2L, false, "Business & Networking" }, + { 3L, false, "Arts & Culture" }, + } + ); + + migrationBuilder.InsertData( + table: "Gatherings", + columns: new[] + { + "GatheringId", + "CancellationDateTime", + "CoverSrc", + "Description", + "End", + "Location", + "Name", + "OrganiserId", + "Start", + }, + values: new object[,] + { + { + 1L, + null, + "", + "A comprehensive summit exploring the latest in AI and machine learning", + new DateTimeOffset( + new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Marina Bay Sands Convention Centre, Singapore", + "Tech Innovation Summit", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 2L, + null, + "", + "Connect with fellow entrepreneurs and investors", + new DateTimeOffset( + new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Clarke Quay Central, Singapore", + "Startup Networking Night", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 3L, + null, + "", + "Showcasing contemporary digital art from emerging artists", + new DateTimeOffset( + new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "National Gallery Singapore", + "Digital Art Exhibition", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 4L, + null, + "", + "Learn modern web development techniques and best practices", + new DateTimeOffset( + new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore Science Centre", + "Web Development Workshop", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 5L, + null, + "", + "Advanced strategies for scaling your business", + new DateTimeOffset( + new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Raffles City Convention Centre, Singapore", + "Business Strategy Seminar", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 6L, + null, + "", + "Professional photography techniques and portfolio building", + new DateTimeOffset( + new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Gardens by the Bay, Singapore", + "Photography Masterclass", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 7L, + null, + "", + "Intensive bootcamp covering iOS and Android development", + new DateTimeOffset( + new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "NUS School of Computing, Singapore", + "Mobile App Development Bootcamp", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 8L, + null, + "", + "Learn about personal finance and investment strategies", + new DateTimeOffset( + new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Suntec Singapore Convention Centre", + "Investment & Finance Forum", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 9L, + null, + "", + "Explore storytelling techniques and creative expression", + new DateTimeOffset( + new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Esplanade Theatres, Singapore", + "Creative Writing Workshop", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 10L, + null, + "", + "Latest trends in cloud architecture and DevOps", + new DateTimeOffset( + new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore EXPO", + "Cloud Computing Conference", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 11L, + null, + "", + "Build and scale your online business effectively", + new DateTimeOffset( + new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Marina Bay Financial Centre, Singapore", + "E-commerce Mastery", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 12L, + null, + "", + "An evening of modern dance and artistic expression", + new DateTimeOffset( + new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Victoria Theatre, Singapore", + "Contemporary Dance Performance", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 13L, + null, + "", + "Essential cybersecurity practices for businesses", + new DateTimeOffset( + new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore Management University", + "Cybersecurity Awareness Training", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 14L, + null, + "", + "Develop essential leadership skills for modern managers", + new DateTimeOffset( + new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Orchard Hotel Singapore", + "Leadership Excellence Workshop", + "empty-user-12345", + new DateTimeOffset( + new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + { + 15L, + null, + "", + "Independent filmmakers present their latest works", + new DateTimeOffset( + new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + "Singapore International Film Festival Venue", + "Film & Media Production Showcase", + "guest-user-22222", + new DateTimeOffset( + new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + }, + } + ); + + migrationBuilder.InsertData( + table: "Bookings", + columns: new[] + { + "BookingId", + "AttendeeId", + "CancellationDateTime", + "CheckInDateTime", + "CheckoutDateTime", + "CreationDateTime", + "GatheringId", + }, + values: new object[,] + { + { + "book_abc123456", + "guest-user-22222", + null, + null, + null, + new DateTimeOffset( + new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + 1L, + }, + { + "book_def789012", + "empty-user-12345", + null, + null, + null, + new DateTimeOffset( + new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + 2L, + }, + } + ); + + migrationBuilder.InsertData( + table: "GatheringCategoryDetails", + columns: new[] { "CategoryId", "GatheringId" }, + values: new object[,] + { + { 1L, 1L }, + { 2L, 2L }, + { 3L, 3L }, + { 1L, 4L }, + { 2L, 5L }, + { 3L, 6L }, + { 1L, 7L }, + { 2L, 8L }, + { 3L, 9L }, + { 1L, 10L }, + { 2L, 11L }, + { 3L, 12L }, + { 1L, 13L }, + { 2L, 14L }, + { 3L, 15L }, + } + ); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId" + ); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL" + ); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId" + ); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId" + ); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId" + ); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail" + ); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL" + ); + + migrationBuilder.CreateIndex( + name: "IX_Bookings_AttendeeId", + table: "Bookings", + column: "AttendeeId" + ); + + migrationBuilder.CreateIndex( + name: "IX_Bookings_GatheringId", + table: "Bookings", + column: "GatheringId" + ); + + migrationBuilder.CreateIndex( + name: "IX_GatheringCategoryDetails_CategoryId", + table: "GatheringCategoryDetails", + column: "CategoryId" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "AspNetRoleClaims"); + + migrationBuilder.DropTable(name: "AspNetUserClaims"); + + migrationBuilder.DropTable(name: "AspNetUserLogins"); + + migrationBuilder.DropTable(name: "AspNetUserRoles"); + + migrationBuilder.DropTable(name: "AspNetUserTokens"); + + migrationBuilder.DropTable(name: "Bookings"); + + migrationBuilder.DropTable(name: "GatheringCategoryDetails"); + + migrationBuilder.DropTable(name: "AspNetRoles"); + + migrationBuilder.DropTable(name: "AspNetUsers"); + + migrationBuilder.DropTable(name: "Categories"); + + migrationBuilder.DropTable(name: "Gatherings"); + } + } } diff --git a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs index e997e59..d8bf36f 100644 --- a/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs +++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs @@ -5,599 +5,599 @@ namespace Evently.Server.Common.Data.Migrations { - /// - public partial class UpdateSeededDates : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_abc123456", - column: "CreationDateTime", - value: new DateTimeOffset( - new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ) - ); - - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_def789012", - column: "CreationDateTime", - value: new DateTimeOffset( - new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ) - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 1L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 2L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 3L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 4L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 5L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 6L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 7L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 8L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 9L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 10L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 11L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 12L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 13L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 14L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 15L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_abc123456", - column: "CreationDateTime", - value: new DateTimeOffset( - new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ) - ); - - migrationBuilder.UpdateData( - table: "Bookings", - keyColumn: "BookingId", - keyValue: "book_def789012", - column: "CreationDateTime", - value: new DateTimeOffset( - new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ) - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 1L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 2L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 3L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 4L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 5L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 6L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 7L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 8L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 9L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 10L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 11L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 12L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 13L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 14L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - - migrationBuilder.UpdateData( - table: "Gatherings", - keyColumn: "GatheringId", - keyValue: 15L, - columns: new[] { "End", "Start" }, - values: new object[] - { - new DateTimeOffset( - new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - new DateTimeOffset( - new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), - new TimeSpan(0, 0, 0, 0, 0) - ), - } - ); - } - } + /// + public partial class UpdateSeededDates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_abc123456", + column: "CreationDateTime", + value: new DateTimeOffset( + new DateTime(2025, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); + + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_def789012", + column: "CreationDateTime", + value: new DateTimeOffset( + new DateTime(2025, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 1L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 2L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 3L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 4L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 5L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 6L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 7L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 8L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 9L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 10L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 11L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 12L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 13L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 14L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 15L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2027, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2027, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_abc123456", + column: "CreationDateTime", + value: new DateTimeOffset( + new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); + + migrationBuilder.UpdateData( + table: "Bookings", + keyColumn: "BookingId", + keyValue: "book_def789012", + column: "CreationDateTime", + value: new DateTimeOffset( + new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ) + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 1L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 2L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 3L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 4L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 5L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 6L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 7L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 8L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 9L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 10L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 11L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 12L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 13L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 14L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + + migrationBuilder.UpdateData( + table: "Gatherings", + keyColumn: "GatheringId", + keyValue: 15L, + columns: new[] { "End", "Start" }, + values: new object[] + { + new DateTimeOffset( + new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + new DateTimeOffset( + new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), + new TimeSpan(0, 0, 0, 0, 0) + ), + } + ); + } + } } diff --git a/src/Evently.Server/Common/Extensions/LoggerExtension.cs b/src/Evently.Server/Common/Extensions/LoggerExtension.cs index a9c6f7d..19cea1c 100644 --- a/src/Evently.Server/Common/Extensions/LoggerExtension.cs +++ b/src/Evently.Server/Common/Extensions/LoggerExtension.cs @@ -2,44 +2,44 @@ public static partial class LoggerExtension { - [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "{key}: {value}")] - public static partial void LogValue(this ILogger logger, string key, string? value); + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "{key}: {value}")] + public static partial void LogValue(this ILogger logger, string key, string? value); - [LoggerMessage( - EventId = 2, - Level = LogLevel.Information, - Message = "Callback Url: {callbackUrl}" - )] - public static partial void LogCallbackUrl(this ILogger logger, string? callbackUrl); + [LoggerMessage( + EventId = 2, + Level = LogLevel.Information, + Message = "Callback Url: {callbackUrl}" + )] + public static partial void LogCallbackUrl(this ILogger logger, string? callbackUrl); - [LoggerMessage( - EventId = 3, - Level = LogLevel.Information, - Message = "Email sent successfully to {email}" - )] - public static partial void LogSuccessEmail(this ILogger logger, string? email); + [LoggerMessage( + EventId = 3, + Level = LogLevel.Information, + Message = "Email sent successfully to {email}" + )] + public static partial void LogSuccessEmail(this ILogger logger, string? email); - [LoggerMessage( - EventId = 4, - Level = LogLevel.Error, - Message = "Error occurred at {context}: {errorMsg}" - )] - public static partial void LogErrorContext( - this ILogger logger, - string context, - string errorMsg - ); + [LoggerMessage( + EventId = 4, + Level = LogLevel.Error, + Message = "Error occurred at {context}: {errorMsg}" + )] + public static partial void LogErrorContext( + this ILogger logger, + string context, + string errorMsg + ); - [LoggerMessage( - EventId = 5, - Level = LogLevel.Error, - Message = "Analyze image failed. Status code: {statusCode}, Error code: {errorCode}, Error message: {errMsg}" - )] - public static partial void LogContentModerationError( - this ILogger logger, - string statusCode, - string errorCode, - string errMsg - ); - // + [LoggerMessage( + EventId = 5, + Level = LogLevel.Error, + Message = "Analyze image failed. Status code: {statusCode}, Error code: {errorCode}, Error message: {errMsg}" + )] + public static partial void LogContentModerationError( + this ILogger logger, + string statusCode, + string errorCode, + string errMsg + ); + // } diff --git a/src/Evently.Server/Common/Extensions/MapperExtension.cs b/src/Evently.Server/Common/Extensions/MapperExtension.cs index 10f9c13..f348bef 100644 --- a/src/Evently.Server/Common/Extensions/MapperExtension.cs +++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs @@ -5,68 +5,68 @@ namespace Evently.Server.Common.Extensions; public static class MapperExtension { - public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) - { - List gatheringCategoryDetails = gatheringReqDto - .GatheringCategoryDetails.Select( - (detail) => - new GatheringCategoryDetail - { - GatheringId = gatheringReqDto.GatheringId, - CategoryId = detail.CategoryId, - } - ) - .ToList(); - Gathering gathering = new() - { - GatheringId = gatheringReqDto.GatheringId, - Name = gatheringReqDto.Name, - Description = gatheringReqDto.Description, - Start = gatheringReqDto.Start, - End = gatheringReqDto.End, - CancellationDateTime = gatheringReqDto.CancellationDateTime, - Location = gatheringReqDto.Location, - OrganiserId = gatheringReqDto.OrganiserId, - CoverSrc = gatheringReqDto.CoverSrc, - GatheringCategoryDetails = gatheringCategoryDetails, - }; - return gathering; - } + public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) + { + List gatheringCategoryDetails = gatheringReqDto + .GatheringCategoryDetails.Select( + (detail) => + new GatheringCategoryDetail + { + GatheringId = gatheringReqDto.GatheringId, + CategoryId = detail.CategoryId, + } + ) + .ToList(); + Gathering gathering = new() + { + GatheringId = gatheringReqDto.GatheringId, + Name = gatheringReqDto.Name, + Description = gatheringReqDto.Description, + Start = gatheringReqDto.Start, + End = gatheringReqDto.End, + CancellationDateTime = gatheringReqDto.CancellationDateTime, + Location = gatheringReqDto.Location, + OrganiserId = gatheringReqDto.OrganiserId, + CoverSrc = gatheringReqDto.CoverSrc, + GatheringCategoryDetails = gatheringCategoryDetails, + }; + return gathering; + } - public static Booking ToBooking(this BookingReqDto bookingReqDto) - { - return new Booking - { - BookingId = bookingReqDto.BookingId, - AttendeeId = bookingReqDto.AttendeeId, - GatheringId = bookingReqDto.GatheringId, - CreationDateTime = bookingReqDto.CreationDateTime, - CheckInDateTime = bookingReqDto.CheckInDateTime, - CheckoutDateTime = bookingReqDto.CheckoutDateTime, - CancellationDateTime = bookingReqDto.CancellationDateTime, - }; - } + public static Booking ToBooking(this BookingReqDto bookingReqDto) + { + return new Booking + { + BookingId = bookingReqDto.BookingId, + AttendeeId = bookingReqDto.AttendeeId, + GatheringId = bookingReqDto.GatheringId, + CreationDateTime = bookingReqDto.CreationDateTime, + CheckInDateTime = bookingReqDto.CheckInDateTime, + CheckoutDateTime = bookingReqDto.CheckoutDateTime, + CancellationDateTime = bookingReqDto.CancellationDateTime, + }; + } - public static BookingReqDto ToBookingDto(this Booking booking) - { - return new BookingReqDto( - booking.BookingId, - booking.AttendeeId, - booking.GatheringId, - booking.CreationDateTime, - booking.CheckInDateTime, - booking.CheckoutDateTime, - booking.CancellationDateTime - ); - } + public static BookingReqDto ToBookingDto(this Booking booking) + { + return new BookingReqDto( + booking.BookingId, + booking.AttendeeId, + booking.GatheringId, + booking.CreationDateTime, + booking.CheckInDateTime, + booking.CheckoutDateTime, + booking.CancellationDateTime + ); + } - public static AccountDto ToAccountDto(this Account account) - { - return new AccountDto( - account.Id, - Email: account.Email ?? string.Empty, - Username: account.UserName ?? string.Empty, - account.Name - ); - } + public static AccountDto ToAccountDto(this Account account) + { + return new AccountDto( + account.Id, + Email: account.Email ?? string.Empty, + Username: account.UserName ?? string.Empty, + account.Name + ); + } } diff --git a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs index 71c2eb8..a9cee08 100644 --- a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs +++ b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs @@ -5,22 +5,22 @@ namespace Evently.Server.Common.Extensions; public static class ServiceContainerExtensions { - public static IOptions LoadAppConfiguration( - this IServiceCollection services, - ConfigurationManager configuration - ) - { - // load .env variables, in addition to appsettings.json that is loaded by default - configuration.AddEnvironmentVariables(); + public static IOptions LoadAppConfiguration( + this IServiceCollection services, + ConfigurationManager configuration + ) + { + // load .env variables, in addition to appsettings.json that is loaded by default + configuration.AddEnvironmentVariables(); - // Inject IOptions into the App - services.Configure(configuration); + // Inject IOptions into the App + services.Configure(configuration); - // Bind all key value pairs to the Settings Object and return it, as it is used in Program.cs - Settings settings = new(); - configuration.Bind(settings); + // Bind all key value pairs to the Settings Object and return it, as it is used in Program.cs + Settings settings = new(); + configuration.Bind(settings); - IOptions options = Options.Create(settings); - return options; - } + IOptions options = Options.Create(settings); + return options; + } } diff --git a/src/Evently.Server/Common/Extensions/UtilsExtension.cs b/src/Evently.Server/Common/Extensions/UtilsExtension.cs index c90b9bd..d9ce25e 100644 --- a/src/Evently.Server/Common/Extensions/UtilsExtension.cs +++ b/src/Evently.Server/Common/Extensions/UtilsExtension.cs @@ -2,22 +2,22 @@ public static class UtilsExtension { - public static Uri RootUri(this HttpRequest request) - { - UriBuilder uriBuilder = new() { Scheme = request.Scheme, Host = request.Host.Host }; - if (request.Host.Port.HasValue) - { - uriBuilder.Port = request.Host.Port.Value; - } + public static Uri RootUri(this HttpRequest request) + { + UriBuilder uriBuilder = new() { Scheme = request.Scheme, Host = request.Host.Host }; + if (request.Host.Port.HasValue) + { + uriBuilder.Port = request.Host.Port.Value; + } - return uriBuilder.Uri; - } + return uriBuilder.Uri; + } - public static async Task ToBinaryData(this IFormFile file) - { - using MemoryStream ms = new(); - await file.CopyToAsync(ms); - byte[] bytes = ms.ToArray(); - return BinaryData.FromBytes(bytes); - } + public static async Task ToBinaryData(this IFormFile file) + { + using MemoryStream ms = new(); + await file.CopyToAsync(ms); + byte[] bytes = ms.ToArray(); + return BinaryData.FromBytes(bytes); + } } diff --git a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs index b2be2c8..4695484 100644 --- a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs +++ b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs @@ -4,47 +4,47 @@ namespace Evently.Server.Common.Middlewares; public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler { - public async ValueTask TryHandleAsync( - HttpContext httpContext, - Exception exception, - CancellationToken cancellationToken - ) - { - string exceptionMessage = exception.Message; - logger.LogError( - "Error Message: {exceptionMessage}, Time of occurrence {time}", - exceptionMessage, - DateTime.UtcNow - ); + public async ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken + ) + { + string exceptionMessage = exception.Message; + logger.LogError( + "Error Message: {exceptionMessage}, Time of occurrence {time}", + exceptionMessage, + DateTime.UtcNow + ); - switch (exception) - { - case ArgumentException: - httpContext.Response.StatusCode = 400; - await httpContext.Response.WriteAsJsonAsync( - value: new - { - Title = "Validation Error", - Detail = exceptionMessage, - Status = StatusCodes.Status400BadRequest, - }, - cancellationToken - ); - break; - default: - httpContext.Response.StatusCode = 500; - await httpContext.Response.WriteAsJsonAsync( - value: new - { - Title = "Server Error", - Detail = "An unexpected error occurred.", - Status = StatusCodes.Status500InternalServerError, - }, - cancellationToken - ); - break; - } + switch (exception) + { + case ArgumentException: + httpContext.Response.StatusCode = 400; + await httpContext.Response.WriteAsJsonAsync( + value: new + { + Title = "Validation Error", + Detail = exceptionMessage, + Status = StatusCodes.Status400BadRequest, + }, + cancellationToken + ); + break; + default: + httpContext.Response.StatusCode = 500; + await httpContext.Response.WriteAsJsonAsync( + value: new + { + Title = "Server Error", + Detail = "An unexpected error occurred.", + Status = StatusCodes.Status500InternalServerError, + }, + cancellationToken + ); + break; + } - return true; - } + return true; + } } diff --git a/src/Evently.Server/Domains/Entities/Account.cs b/src/Evently.Server/Domains/Entities/Account.cs index fcf8268..e24c4ab 100644 --- a/src/Evently.Server/Domains/Entities/Account.cs +++ b/src/Evently.Server/Domains/Entities/Account.cs @@ -9,7 +9,7 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] public class Account : IdentityUser { - [StringLength(100)] - public string Name { get; set; } = string.Empty; - public List Bookings { get; set; } = []; + [StringLength(100)] + public string Name { get; set; } = string.Empty; + public List Bookings { get; set; } = []; } diff --git a/src/Evently.Server/Domains/Entities/Booking.cs b/src/Evently.Server/Domains/Entities/Booking.cs index 088cca0..b04cccd 100644 --- a/src/Evently.Server/Domains/Entities/Booking.cs +++ b/src/Evently.Server/Domains/Entities/Booking.cs @@ -12,30 +12,30 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "UnusedMember.Global")] public class Booking { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - [StringLength(50)] - public string BookingId { get; set; } = $"book_{Nanoid.Generate(size: 10)}"; + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [StringLength(50)] + public string BookingId { get; set; } = $"book_{Nanoid.Generate(size: 10)}"; - [ForeignKey("Account")] - [SuppressMessage( - "ReSharper", - "EntityFramework.ModelValidation.UnlimitedStringLength", - Justification = "MSSQL does not allow specified string limit" - )] - public string AttendeeId { get; set; } = string.Empty; + [ForeignKey("Account")] + [SuppressMessage( + "ReSharper", + "EntityFramework.ModelValidation.UnlimitedStringLength", + Justification = "MSSQL does not allow specified string limit" + )] + public string AttendeeId { get; set; } = string.Empty; - [JsonIgnore] - public Account? Account { get; set; } + [JsonIgnore] + public Account? Account { get; set; } - [NotMapped] - public AccountDto? AccountDto => Account?.ToAccountDto(); + [NotMapped] + public AccountDto? AccountDto => Account?.ToAccountDto(); - public long GatheringId { get; set; } - public Gathering? Gathering { get; set; } + public long GatheringId { get; set; } + public Gathering? Gathering { get; set; } - public DateTimeOffset CreationDateTime { get; set; } = DateTimeOffset.UtcNow; - public DateTimeOffset? CheckInDateTime { get; set; } - public DateTimeOffset? CheckoutDateTime { get; set; } - public DateTimeOffset? CancellationDateTime { get; set; } + public DateTimeOffset CreationDateTime { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset? CheckInDateTime { get; set; } + public DateTimeOffset? CheckoutDateTime { get; set; } + public DateTimeOffset? CancellationDateTime { get; set; } } diff --git a/src/Evently.Server/Domains/Entities/Category.cs b/src/Evently.Server/Domains/Entities/Category.cs index bd36b2e..38e6cf3 100644 --- a/src/Evently.Server/Domains/Entities/Category.cs +++ b/src/Evently.Server/Domains/Entities/Category.cs @@ -7,12 +7,12 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] public class Category { - [Key] - public long CategoryId { get; set; } + [Key] + public long CategoryId { get; set; } - [StringLength(100)] - public string CategoryName { get; set; } = string.Empty; - public bool Approved { get; set; } + [StringLength(100)] + public string CategoryName { get; set; } = string.Empty; + public bool Approved { get; set; } - public List GatheringCategoryDetails { get; set; } = []; + public List GatheringCategoryDetails { get; set; } = []; } diff --git a/src/Evently.Server/Domains/Entities/Gathering.cs b/src/Evently.Server/Domains/Entities/Gathering.cs index 49a7f2c..e6c6936 100644 --- a/src/Evently.Server/Domains/Entities/Gathering.cs +++ b/src/Evently.Server/Domains/Entities/Gathering.cs @@ -8,32 +8,32 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] public class Gathering { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long GatheringId { get; set; } + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long GatheringId { get; set; } - [StringLength(100)] - public string Name { get; set; } = string.Empty; + [StringLength(100)] + public string Name { get; set; } = string.Empty; - [StringLength(10_000)] - public string Description { get; set; } = string.Empty; + [StringLength(10_000)] + public string Description { get; set; } = string.Empty; - public DateTimeOffset Start { get; set; } = DateTimeOffset.UtcNow; - public DateTimeOffset End { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset Start { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset End { get; set; } = DateTimeOffset.UtcNow; - [StringLength(100)] - public string Location { get; set; } = string.Empty; + [StringLength(100)] + public string Location { get; set; } = string.Empty; - [StringLength(1000)] - public string? CoverSrc { get; set; } = string.Empty; + [StringLength(1000)] + public string? CoverSrc { get; set; } = string.Empty; - // convenience field that acts as a readonly field for Account that created the Gathering - [ForeignKey("Account")] - [StringLength(100)] - public string OrganiserId { get; set; } = string.Empty; + // convenience field that acts as a readonly field for Account that created the Gathering + [ForeignKey("Account")] + [StringLength(100)] + public string OrganiserId { get; set; } = string.Empty; - public DateTimeOffset? CancellationDateTime { get; set; } + public DateTimeOffset? CancellationDateTime { get; set; } - public List Bookings { get; set; } = []; - public List GatheringCategoryDetails { get; set; } = []; + public List Bookings { get; set; } = []; + public List GatheringCategoryDetails { get; set; } = []; } diff --git a/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs index 660684d..7781af2 100644 --- a/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs +++ b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs @@ -8,8 +8,8 @@ namespace Evently.Server.Domains.Entities; [SuppressMessage("ReSharper", "UnusedMember.Global")] public class GatheringCategoryDetail { - public long GatheringId { get; set; } - public Gathering? Gathering { get; set; } - public long CategoryId { get; set; } - public Category? Category { get; set; } + public long GatheringId { get; set; } + public Gathering? Gathering { get; set; } + public long CategoryId { get; set; } + public Category? Category { get; set; } } diff --git a/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs index 462a1d6..c97efcb 100644 --- a/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs +++ b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs @@ -1,4 +1,4 @@ namespace Evently.Server.Domains.Exceptions; public class ExternalLoginProviderException(string provider, string message) - : Exception($"External login provider: {provider} error occurred: {message}"); + : Exception($"External login provider: {provider} error occurred: {message}"); diff --git a/src/Evently.Server/Domains/Interfaces/IAccountsService.cs b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs index 8318c4e..2d3c4a4 100644 --- a/src/Evently.Server/Domains/Interfaces/IAccountsService.cs +++ b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs @@ -5,6 +5,6 @@ namespace Evently.Server.Domains.Interfaces; public interface IAccountsService { - Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider); - Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal); + Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider); + Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal); } diff --git a/src/Evently.Server/Domains/Interfaces/IBookingService.cs b/src/Evently.Server/Domains/Interfaces/IBookingService.cs index d5db972..1d42d6b 100644 --- a/src/Evently.Server/Domains/Interfaces/IBookingService.cs +++ b/src/Evently.Server/Domains/Interfaces/IBookingService.cs @@ -5,23 +5,23 @@ namespace Evently.Server.Domains.Interfaces; public interface IBookingService { - Task GetBooking(string bookingId); + Task GetBooking(string bookingId); - Task> GetBookings( - string? accountId, - long? gatheringId, - DateTimeOffset? checkInStart, - DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, - DateTimeOffset? gatheringStartAfter, - DateTimeOffset? gatheringEndBefore, - DateTimeOffset? gatheringEndAfter, - bool? isCancelled, - int? offset, - int? limit - ); + Task> GetBookings( + string? accountId, + long? gatheringId, + DateTimeOffset? checkInStart, + DateTimeOffset? checkInEnd, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, + bool? isCancelled, + int? offset, + int? limit + ); - Task CreateBooking(BookingReqDto bookingReqDto); - Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto); - Task RenderTicket(string bookingId); + Task CreateBooking(BookingReqDto bookingReqDto); + Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto); + Task RenderTicket(string bookingId); } diff --git a/src/Evently.Server/Domains/Interfaces/ICategoryService.cs b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs index 4404146..c4eea00 100644 --- a/src/Evently.Server/Domains/Interfaces/ICategoryService.cs +++ b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs @@ -5,6 +5,6 @@ namespace Evently.Server.Domains.Interfaces; public interface ICategoryService { - Task> GetCategories(long? gatheringId, bool? approved); - Task CreateCategory(Category category); + Task> GetCategories(long? gatheringId, bool? approved); + Task CreateCategory(Category category); } diff --git a/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs index 9422c30..3e3721d 100644 --- a/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs +++ b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs @@ -2,6 +2,6 @@ namespace Evently.Server.Domains.Interfaces; public interface IEmailerAdapter { - // senderEmail: Either actual Sender email or email of the third party that IEmailer sends on behalf of. - Task SendEmailAsync(string senderEmail, string recipientEmail, string subject, string body); + // senderEmail: Either actual Sender email or email of the third party that IEmailer sends on behalf of. + Task SendEmailAsync(string senderEmail, string recipientEmail, string subject, string body); } diff --git a/src/Evently.Server/Domains/Interfaces/IGatheringService.cs b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs index e176e71..d7d0f80 100644 --- a/src/Evently.Server/Domains/Interfaces/IGatheringService.cs +++ b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs @@ -5,23 +5,23 @@ namespace Evently.Server.Domains.Interfaces; public interface IGatheringService { - Task GetGathering(long gatheringId); + Task GetGathering(long gatheringId); - Task> GetGatherings( - string? attendeeId, - string? organiserId, - string? name, - DateTimeOffset? startDateBefore, - DateTimeOffset? startDateAfter, - DateTimeOffset? endDateBefore, - DateTimeOffset? endDateAfter, - bool? isCancelled, - HashSet? categoryIds, - int? offset, - int? limit - ); + Task> GetGatherings( + string? attendeeId, + string? organiserId, + string? name, + DateTimeOffset? startDateBefore, + DateTimeOffset? startDateAfter, + DateTimeOffset? endDateBefore, + DateTimeOffset? endDateAfter, + bool? isCancelled, + HashSet? categoryIds, + int? offset, + int? limit + ); - Task CreateGathering(GatheringReqDto gatheringReqDto); - Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto); - Task DeleteGathering(long gatheringId); + Task CreateGathering(GatheringReqDto gatheringReqDto); + Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto); + Task DeleteGathering(long gatheringId); } diff --git a/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs index 03fa53f..f0296c0 100644 --- a/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs +++ b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs @@ -5,14 +5,14 @@ namespace Evently.Server.Domains.Interfaces; public interface IMediaRenderer { - Task RenderComponentHtml(Dictionary dictionary) - where T : IComponent; - BinaryData RenderQr(string qrData); + Task RenderComponentHtml(Dictionary dictionary) + where T : IComponent; + BinaryData RenderQr(string qrData); - [SuppressMessage( - "ReSharper", - "UnusedMember.Global", - Justification = "May need to convert ticket HTML to PDF in future" - )] - BinaryData RenderPdf(string html); + [SuppressMessage( + "ReSharper", + "UnusedMember.Global", + Justification = "May need to convert ticket HTML to PDF in future" + )] + BinaryData RenderPdf(string html); } diff --git a/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs index a4e3707..6ebdd83 100644 --- a/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs +++ b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs @@ -5,17 +5,17 @@ namespace Evently.Server.Domains.Interfaces; [SuppressMessage("ReSharper", "UnusedMember.Global")] public interface IObjectStorageService { - // mimeType determines the browser's behaviour when the file is accessed directly via the browser, - // e.g. download ("application/octet-stream") or view ("images/*)" the file. - Task UploadFile( - string containerName, - string fileName, - BinaryData binaryData, - string mimeType = "application/octet-stream" - ); + // mimeType determines the browser's behaviour when the file is accessed directly via the browser, + // e.g. download ("application/octet-stream") or view ("images/*)" the file. + Task UploadFile( + string containerName, + string fileName, + BinaryData binaryData, + string mimeType = "application/octet-stream" + ); - Task GetFileUri(string containerName, string fileName); - Task GetFile(string containerName, string fileName); - Task IsFileExists(string containerName, string fileName); - Task PassesContentModeration(BinaryData binaryData); + Task GetFileUri(string containerName, string fileName); + Task GetFile(string containerName, string fileName); + Task IsFileExists(string containerName, string fileName); + Task PassesContentModeration(BinaryData binaryData); } diff --git a/src/Evently.Server/Domains/Models/BookingReqDto.cs b/src/Evently.Server/Domains/Models/BookingReqDto.cs index c750ca6..b1e7357 100644 --- a/src/Evently.Server/Domains/Models/BookingReqDto.cs +++ b/src/Evently.Server/Domains/Models/BookingReqDto.cs @@ -1,11 +1,11 @@ namespace Evently.Server.Domains.Models; public sealed record BookingReqDto( - string BookingId, - string AttendeeId, - long GatheringId, - DateTimeOffset CreationDateTime, - DateTimeOffset? CheckInDateTime, - DateTimeOffset? CheckoutDateTime, - DateTimeOffset? CancellationDateTime + string BookingId, + string AttendeeId, + long GatheringId, + DateTimeOffset CreationDateTime, + DateTimeOffset? CheckInDateTime, + DateTimeOffset? CheckoutDateTime, + DateTimeOffset? CancellationDateTime ); diff --git a/src/Evently.Server/Domains/Models/GatheringReqDto.cs b/src/Evently.Server/Domains/Models/GatheringReqDto.cs index 5c70422..d3ce03f 100644 --- a/src/Evently.Server/Domains/Models/GatheringReqDto.cs +++ b/src/Evently.Server/Domains/Models/GatheringReqDto.cs @@ -1,14 +1,14 @@ namespace Evently.Server.Domains.Models; public sealed record GatheringReqDto( - long GatheringId, - string Name, - string Description, - DateTimeOffset Start, - DateTimeOffset End, - DateTimeOffset? CancellationDateTime, - string Location, - string OrganiserId, - string? CoverSrc, - List GatheringCategoryDetails + long GatheringId, + string Name, + string Description, + DateTimeOffset Start, + DateTimeOffset End, + DateTimeOffset? CancellationDateTime, + string Location, + string OrganiserId, + string? CoverSrc, + List GatheringCategoryDetails ); diff --git a/src/Evently.Server/Domains/Models/PageResult.cs b/src/Evently.Server/Domains/Models/PageResult.cs index 0117a71..9994883 100644 --- a/src/Evently.Server/Domains/Models/PageResult.cs +++ b/src/Evently.Server/Domains/Models/PageResult.cs @@ -2,6 +2,6 @@ namespace Evently.Server.Domains.Models; public sealed class PageResult { - public List Items { get; init; } = []; - public int TotalCount { get; init; } + public List Items { get; init; } = []; + public int TotalCount { get; init; } } diff --git a/src/Evently.Server/Domains/Models/Settings.cs b/src/Evently.Server/Domains/Models/Settings.cs index 7fdb226..95221cd 100644 --- a/src/Evently.Server/Domains/Models/Settings.cs +++ b/src/Evently.Server/Domains/Models/Settings.cs @@ -5,49 +5,49 @@ namespace Evently.Server.Domains.Models; public sealed class Settings { - public StorageAccount StorageAccount { get; init; } = new(); + public StorageAccount StorageAccount { get; init; } = new(); - [NotMapped] - public AuthSetting Authentication { get; init; } = new(); + [NotMapped] + public AuthSetting Authentication { get; init; } = new(); - [NotMapped] - public EmailSettings EmailSettings { get; init; } = new(); + [NotMapped] + public EmailSettings EmailSettings { get; init; } = new(); - [NotMapped] - public AzureAIFoundry AzureAiFoundry { get; init; } = new(); + [NotMapped] + public AzureAIFoundry AzureAiFoundry { get; init; } = new(); } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] public sealed class StorageAccount { - public string AccountName { get; init; } = string.Empty; - public string AzureStorageConnectionString { get; init; } = string.Empty; + public string AccountName { get; init; } = string.Empty; + public string AzureStorageConnectionString { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] public sealed class AuthSetting { - public OAuthSetting Google { get; init; } = new(); + public OAuthSetting Google { get; init; } = new(); } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] public sealed class OAuthSetting { - public string ClientId { get; init; } = string.Empty; - public string ClientSecret { get; init; } = string.Empty; + public string ClientId { get; init; } = string.Empty; + public string ClientSecret { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] public sealed class EmailSettings { - public string ActualFrom { get; init; } = string.Empty; - public string SmtpPassword { get; init; } = string.Empty; + public string ActualFrom { get; init; } = string.Empty; + public string SmtpPassword { get; init; } = string.Empty; } [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] // ReSharper disable once InconsistentNaming public sealed class AzureAIFoundry { - public string ContentSafetyKey { get; init; } = string.Empty; - public string ContentSafetyEndpoint { get; init; } = string.Empty; + public string ContentSafetyKey { get; init; } = string.Empty; + public string ContentSafetyEndpoint { get; init; } = string.Empty; } diff --git a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs index dcb2207..984ebec 100644 --- a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs +++ b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs @@ -15,109 +15,109 @@ namespace Evently.Server.Features.Accounts.Controllers; [ApiController] [Route("api/v1/Auth/external")] public sealed class AccountController( - IAccountsService accountService, - ILogger logger + IAccountsService accountService, + ILogger logger ) : ControllerBase { - private readonly Dictionary _authSchemes = new() - { - { "google", GoogleDefaults.AuthenticationScheme }, - { "microsoft", MicrosoftAccountDefaults.AuthenticationScheme }, - }; + private readonly Dictionary _authSchemes = new() + { + { "google", GoogleDefaults.AuthenticationScheme }, + { "microsoft", MicrosoftAccountDefaults.AuthenticationScheme }, + }; - /** - * Overall auth flow: - * 1. Login from FE browser. FE specified callback URL. - * 2. Lands on "{provider}/login". - * 3. One callback url is appended to the challenge URL in the form of {provider}/callback by the Login method. - * 4. Another callback url is appended to the challenge URL in the form of /signin-google/ by the OAuth middleware in Program.cs. - * 5. User is redirected to the Google Login page. - * 6. On success, redirected to /signin-google/ specified by middleware. - * 7. Middleware redirects to {provider}/callback. - * 8. Callback() method redirects to FE specified callback URL. - */ - [HttpGet("{provider}/login")] - public IActionResult Login(string provider, string? originUrl = "") - { - Uri rootUri = Request.RootUri(); - string uri = Url.Action("Callback", "Account", values: new { provider }) ?? ""; - UriBuilder combined = new(rootUri) { Path = uri, Query = $"originUrl={originUrl}" }; + /** + * Overall auth flow: + * 1. Login from FE browser. FE specified callback URL. + * 2. Lands on "{provider}/login". + * 3. One callback url is appended to the challenge URL in the form of {provider}/callback by the Login method. + * 4. Another callback url is appended to the challenge URL in the form of /signin-google/ by the OAuth middleware in Program.cs. + * 5. User is redirected to the Google Login page. + * 6. On success, redirected to /signin-google/ specified by middleware. + * 7. Middleware redirects to {provider}/callback. + * 8. Callback() method redirects to FE specified callback URL. + */ + [HttpGet("{provider}/login")] + public IActionResult Login(string provider, string? originUrl = "") + { + Uri rootUri = Request.RootUri(); + string uri = Url.Action("Callback", "Account", values: new { provider }) ?? ""; + UriBuilder combined = new(rootUri) { Path = uri, Query = $"originUrl={originUrl}" }; - logger.LogCallbackUrl(combined.Uri.AbsoluteUri); - AuthenticationProperties properties = new() - { - RedirectUri = combined.Uri.AbsoluteUri, - IsPersistent = true, - }; - return Challenge(properties, _authSchemes.GetValueOrDefault(provider) ?? ""); - } + logger.LogCallbackUrl(combined.Uri.AbsoluteUri); + AuthenticationProperties properties = new() + { + RedirectUri = combined.Uri.AbsoluteUri, + IsPersistent = true, + }; + return Challenge(properties, _authSchemes.GetValueOrDefault(provider) ?? ""); + } - [HttpGet("{provider}/callback")] - public async Task> Callback( - string provider, - [FromQuery] string originUrl = "" - ) - { - AuthenticateResult result = await HttpContext.AuthenticateAsync( - GoogleDefaults.AuthenticationScheme - ); + [HttpGet("{provider}/callback")] + public async Task> Callback( + string provider, + [FromQuery] string originUrl = "" + ) + { + AuthenticateResult result = await HttpContext.AuthenticateAsync( + GoogleDefaults.AuthenticationScheme + ); - if (!result.Succeeded || result.Principal is null) - { - return Unauthorized(); - } + if (!result.Succeeded || result.Principal is null) + { + return Unauthorized(); + } - ClaimsPrincipal claimsPrincipal = result.Principal; - if (claimsPrincipal == null) - { - throw new ExternalLoginProviderException(provider, "ClaimsPrincipal is null"); - } + ClaimsPrincipal claimsPrincipal = result.Principal; + if (claimsPrincipal == null) + { + throw new ExternalLoginProviderException(provider, "ClaimsPrincipal is null"); + } - await accountService.ExternalLogin( - claimsPrincipal, - loginProvider: _authSchemes.GetValueOrDefault(provider) ?? "" - ); - return Redirect(originUrl); - } + await accountService.ExternalLogin( + claimsPrincipal, + loginProvider: _authSchemes.GetValueOrDefault(provider) ?? "" + ); + return Redirect(originUrl); + } - [HttpGet("account", Name = "GetAccount from Cookie")] - public async Task GetAccount() - { - AuthenticateResult result = await HttpContext.AuthenticateAsync( - IdentityConstants.ExternalScheme - ); - if (!result.Succeeded) - { - return Unauthorized(); - } + [HttpGet("account", Name = "GetAccount from Cookie")] + public async Task GetAccount() + { + AuthenticateResult result = await HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!result.Succeeded) + { + return Unauthorized(); + } - ClaimsPrincipal principal = result.Principal ?? new ClaimsPrincipal(); - Account? user = await accountService.FindByClaimsPrincipalAsync(principal); - if (user is null) - { - return NotFound(new { message = "User not found" }); - } + ClaimsPrincipal principal = result.Principal ?? new ClaimsPrincipal(); + Account? user = await accountService.FindByClaimsPrincipalAsync(principal); + if (user is null) + { + return NotFound(new { message = "User not found" }); + } - return Ok(user.ToAccountDto()); - } + return Ok(user.ToAccountDto()); + } - [HttpPost("logout")] - public async Task Logout(string? redirectUrl = "") - { - // Sign out of an external identity provider (if used) - AuthenticationProperties authProps = new() - { - RedirectUri = redirectUrl, - IsPersistent = true, - }; - await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme, authProps); - // Manually remove the authentication cookie - // Delete each cookie - foreach (string cookieName in Request.Cookies.Keys) - { - Response.Cookies.Delete(cookieName); - } + [HttpPost("logout")] + public async Task Logout(string? redirectUrl = "") + { + // Sign out of an external identity provider (if used) + AuthenticationProperties authProps = new() + { + RedirectUri = redirectUrl, + IsPersistent = true, + }; + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme, authProps); + // Manually remove the authentication cookie + // Delete each cookie + foreach (string cookieName in Request.Cookies.Keys) + { + Response.Cookies.Delete(cookieName); + } - return Ok(new { redirectUrl }); - } + return Ok(new { redirectUrl }); + } } diff --git a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs index aff926f..686bbbb 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountAuthorizationHandler.cs @@ -7,39 +7,39 @@ namespace Evently.Server.Features.Accounts.Services; // Based on https://tinyurl.com/5cxw9vmu public sealed class AccountAuthorizationHandler(UserManager userManager) - : AuthorizationHandler + : AuthorizationHandler { - protected override async Task HandleRequirementAsync( - AuthorizationHandlerContext context, - SameAccountRequirement requirement, - string? identityUserId - ) - { - ClaimsPrincipal principal = context.User; - Account? user = await FindByClaimsPrincipalAsync(userManager, principal); + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, + SameAccountRequirement requirement, + string? identityUserId + ) + { + ClaimsPrincipal principal = context.User; + Account? user = await FindByClaimsPrincipalAsync(userManager, principal); - bool userMatch = user is not null && user.Id == identityUserId; - if (userMatch) - { - context.Succeed(requirement); - } - } + bool userMatch = user is not null && user.Id == identityUserId; + if (userMatch) + { + context.Succeed(requirement); + } + } - private static async Task FindByClaimsPrincipalAsync( - UserManager userManager, - ClaimsPrincipal claimsPrincipal - ) - { - string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; - string providerKey = - claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + private static async Task FindByClaimsPrincipalAsync( + UserManager userManager, + ClaimsPrincipal claimsPrincipal + ) + { + string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; - Account? user = await userManager.GetUserAsync(claimsPrincipal); - return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); - } + Account? user = await userManager.GetUserAsync(claimsPrincipal); + return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); + } } public class SameAccountRequirement : IAuthorizationRequirement { - public const string PolicyName = "SameAccountPolicy"; + public const string PolicyName = "SameAccountPolicy"; } diff --git a/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs b/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs index b289a33..e3232f9 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountExtensions.cs @@ -8,28 +8,28 @@ namespace Evently.Server.Features.Accounts.Services; public static class AccountExtensions { - public static async Task IsResourceOwner( - this ControllerBase controller, - object? resourceIdentityUserId - ) - { - IAuthorizationService authorizationService = - controller.HttpContext.RequestServices.GetRequiredService(); + public static async Task IsResourceOwner( + this ControllerBase controller, + object? resourceIdentityUserId + ) + { + IAuthorizationService authorizationService = + controller.HttpContext.RequestServices.GetRequiredService(); - AuthenticateResult authenticationResult = await controller.HttpContext.AuthenticateAsync( - IdentityConstants.ExternalScheme - ); - if (!authenticationResult.Succeeded || resourceIdentityUserId is null) - { - return false; - } + AuthenticateResult authenticationResult = await controller.HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!authenticationResult.Succeeded || resourceIdentityUserId is null) + { + return false; + } - ClaimsPrincipal principal = authenticationResult.Principal ?? new ClaimsPrincipal(); - AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync( - principal, - resourceIdentityUserId, - SameAccountRequirement.PolicyName - ); - return authorizationResult.Succeeded; - } + ClaimsPrincipal principal = authenticationResult.Principal ?? new ClaimsPrincipal(); + AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync( + principal, + resourceIdentityUserId, + SameAccountRequirement.PolicyName + ); + return authorizationResult.Succeeded; + } } diff --git a/src/Evently.Server/Features/Accounts/Services/AccountService.cs b/src/Evently.Server/Features/Accounts/Services/AccountService.cs index adb8b1e..db8d6cb 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountService.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountService.cs @@ -10,79 +10,79 @@ namespace Evently.Server.Features.Accounts.Services; // Based on https://tinyurl.com/4u4r7ywy public sealed partial class AccountService(UserManager userManager) : IAccountsService { - public async Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider) - { - string providerKey = - claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; - UserLoginInfo info = new(loginProvider, providerKey, loginProvider); + public async Task ExternalLogin(ClaimsPrincipal claimsPrincipal, string loginProvider) + { + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + UserLoginInfo info = new(loginProvider, providerKey, loginProvider); - Account user = - await userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey) - ?? await CreateExternalUser(claimsPrincipal, loginProvider); - await userManager.AddLoginAsync(user, info); - return user; - } + Account user = + await userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey) + ?? await CreateExternalUser(claimsPrincipal, loginProvider); + await userManager.AddLoginAsync(user, info); + return user; + } - public async Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal) - { - string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; - string providerKey = - claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + public async Task FindByClaimsPrincipalAsync(ClaimsPrincipal claimsPrincipal) + { + string loginProvider = claimsPrincipal.Identity?.AuthenticationType ?? string.Empty; + string providerKey = + claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; - Account? user = await userManager.GetUserAsync(claimsPrincipal); - return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); - } + Account? user = await userManager.GetUserAsync(claimsPrincipal); + return user ?? await userManager.FindByLoginAsync(loginProvider, providerKey); + } - private async Task CreateExternalUser( - ClaimsPrincipal claimsPrincipal, - string loginProvider - ) - { - UserLoginInfo info = new( - loginProvider, - providerKey: claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty, - loginProvider - ); - string? email = claimsPrincipal.FindFirstValue(ClaimTypes.Email); - if (email == null) - { - throw new ExternalLoginProviderException(loginProvider, "Email is null"); - } + private async Task CreateExternalUser( + ClaimsPrincipal claimsPrincipal, + string loginProvider + ) + { + UserLoginInfo info = new( + loginProvider, + providerKey: claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty, + loginProvider + ); + string? email = claimsPrincipal.FindFirstValue(ClaimTypes.Email); + if (email == null) + { + throw new ExternalLoginProviderException(loginProvider, "Email is null"); + } - string username = claimsPrincipal.FindFirstValue(ClaimTypes.Name) ?? string.Empty; - username = UsernameRegex().Replace(username, ""); + string username = claimsPrincipal.FindFirstValue(ClaimTypes.Name) ?? string.Empty; + username = UsernameRegex().Replace(username, ""); - Account newUser = new() - { - UserName = username, - Email = email, - EmailConfirmed = true, - }; + Account newUser = new() + { + UserName = username, + Email = email, + EmailConfirmed = true, + }; - IdentityResult result = await userManager.CreateAsync(newUser); + IdentityResult result = await userManager.CreateAsync(newUser); - if (!result.Succeeded) - { - throw new ExternalLoginProviderException( - loginProvider, - message: $"Unable to create user: {string.Join(", ", + if (!result.Succeeded) + { + throw new ExternalLoginProviderException( + loginProvider, + message: $"Unable to create user: {string.Join(", ", values: result.Errors.Select(x => x.Description))}" - ); - } + ); + } - IdentityResult loginResult = await userManager.AddLoginAsync(newUser, info); - if (!loginResult.Succeeded) - { - throw new ExternalLoginProviderException( - loginProvider, - message: $"Unable to login user: {string.Join(", ", + IdentityResult loginResult = await userManager.AddLoginAsync(newUser, info); + if (!loginResult.Succeeded) + { + throw new ExternalLoginProviderException( + loginProvider, + message: $"Unable to login user: {string.Join(", ", values: loginResult.Errors.Select(err => err.Description))}" - ); - } + ); + } - return newUser; - } + return newUser; + } - [GeneratedRegex("[^a-zA-Z0-9]")] - private static partial Regex UsernameRegex(); + [GeneratedRegex("[^a-zA-Z0-9]")] + private static partial Regex UsernameRegex(); } diff --git a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs index c88c199..cc1d84a 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs @@ -5,24 +5,24 @@ namespace Evently.Server.Features.Accounts.Services; public sealed class AccountValidator : AbstractValidator { - public AccountValidator() - { - RuleFor((member) => member.Name).NotEmpty().WithMessage("Name is required."); - RuleFor((member) => member.Email).NotEmpty().WithMessage("Email is required."); - RuleForEach((member) => member.Bookings) - .Custom( - (value, context) => - { - if (value.AttendeeId == string.Empty) - { - context.AddFailure("MemberId is required."); - } + public AccountValidator() + { + RuleFor((member) => member.Name).NotEmpty().WithMessage("Name is required."); + RuleFor((member) => member.Email).NotEmpty().WithMessage("Email is required."); + RuleForEach((member) => member.Bookings) + .Custom( + (value, context) => + { + if (value.AttendeeId == string.Empty) + { + context.AddFailure("MemberId is required."); + } - if (string.IsNullOrEmpty(value.BookingId)) - { - context.AddFailure("BookingId is required."); - } - } - ); - } + if (string.IsNullOrEmpty(value.BookingId)) + { + context.AddFailure("BookingId is required."); + } + } + ); + } } diff --git a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs index 8690990..433717a 100644 --- a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs +++ b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs @@ -12,122 +12,122 @@ namespace Evently.Server.Features.Bookings.Controllers; [ApiController] [Route("api/v1/[controller]")] public sealed class BookingsController( - IBookingService bookingService, - ChannelWriter emailQueue, - ILogger logger + IBookingService bookingService, + ChannelWriter emailQueue, + ILogger logger ) : ControllerBase { - [HttpGet("{bookingId}", Name = "GetBooking")] - public async Task> GetBooking(string bookingId) - { - Booking? booking = await bookingService.GetBooking(bookingId); - if (booking is null) - { - return NotFound(); - } + [HttpGet("{bookingId}", Name = "GetBooking")] + public async Task> GetBooking(string bookingId) + { + Booking? booking = await bookingService.GetBooking(bookingId); + if (booking is null) + { + return NotFound(); + } - return Ok(booking); - } + return Ok(booking); + } - [HttpGet("{bookingId}/preview", Name = "PreviewBooking")] - public async Task> PreviewBooking(string bookingId) - { - string html = await bookingService.RenderTicket(bookingId); - return Content(html, "text/html"); - } + [HttpGet("{bookingId}/preview", Name = "PreviewBooking")] + public async Task> PreviewBooking(string bookingId) + { + string html = await bookingService.RenderTicket(bookingId); + return Content(html, "text/html"); + } - [HttpGet("", Name = "GetBookings")] - public async Task> GetBookings( - string? attendeeId, - long? gatheringId, - DateTimeOffset? checkInStart, - DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, - DateTimeOffset? gatheringStartAfter, - DateTimeOffset? gatheringEndBefore, - DateTimeOffset? gatheringEndAfter, - bool isCancelled, - int? offset, - int? limit - ) - { - PageResult result = await bookingService.GetBookings( - attendeeId, - gatheringId, - checkInStart, - checkInEnd, - gatheringStartBefore, - gatheringStartAfter, - gatheringEndBefore, - gatheringEndAfter, - isCancelled, - offset, - limit - ); - List bookingEvents = result.Items; - int total = result.TotalCount; - HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append( - "X-Total-Count", - value: total.ToString(CultureInfo.InvariantCulture) - ); - return Ok(bookingEvents); - } + [HttpGet("", Name = "GetBookings")] + public async Task> GetBookings( + string? attendeeId, + long? gatheringId, + DateTimeOffset? checkInStart, + DateTimeOffset? checkInEnd, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, + bool isCancelled, + int? offset, + int? limit + ) + { + PageResult result = await bookingService.GetBookings( + attendeeId, + gatheringId, + checkInStart, + checkInEnd, + gatheringStartBefore, + gatheringStartAfter, + gatheringEndBefore, + gatheringEndAfter, + isCancelled, + offset, + limit + ); + List bookingEvents = result.Items; + int total = result.TotalCount; + HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); + return Ok(bookingEvents); + } - [HttpPost("", Name = "CreateBooking")] - public async Task> CreateBooking([FromBody] BookingReqDto bookingReqDto) - { - Booking booking = await bookingService.CreateBooking(bookingReqDto); - await emailQueue.WriteAsync(booking.BookingId); - return Ok(booking); - } + [HttpPost("", Name = "CreateBooking")] + public async Task> CreateBooking([FromBody] BookingReqDto bookingReqDto) + { + Booking booking = await bookingService.CreateBooking(bookingReqDto); + await emailQueue.WriteAsync(booking.BookingId); + return Ok(booking); + } - [HttpPatch("{bookingId}/cancel", Name = "CancelBooking")] - public async Task CancelBooking(string bookingId) - { - Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Gathering is null) - { - return NotFound(); - } + [HttpPatch("{bookingId}/cancel", Name = "CancelBooking")] + public async Task CancelBooking(string bookingId) + { + Booking? booking = await bookingService.GetBooking(bookingId); + if (booking?.Gathering is null) + { + return NotFound(); + } - bool isAuth = await this.IsResourceOwner(booking.AttendeeId); - logger.LogInformation("isAuth: {}", isAuth); - if (!isAuth) - { - return Forbid(); - } + bool isAuth = await this.IsResourceOwner(booking.AttendeeId); + logger.LogInformation("isAuth: {}", isAuth); + if (!isAuth) + { + return Forbid(); + } - booking.CancellationDateTime = DateTimeOffset.UtcNow; - booking = await bookingService.UpdateBooking( - bookingId, - bookingReqDto: booking.ToBookingDto() - ); - return Ok(booking); - } + booking.CancellationDateTime = DateTimeOffset.UtcNow; + booking = await bookingService.UpdateBooking( + bookingId, + bookingReqDto: booking.ToBookingDto() + ); + return Ok(booking); + } - [HttpPatch("{bookingId}/checkIn", Name = "CheckInBooking")] - public async Task CheckInBooking(string bookingId) - { - Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Gathering is null) - { - return NotFound(); - } + [HttpPatch("{bookingId}/checkIn", Name = "CheckInBooking")] + public async Task CheckInBooking(string bookingId) + { + Booking? booking = await bookingService.GetBooking(bookingId); + if (booking?.Gathering is null) + { + return NotFound(); + } - Gathering gathering = booking.Gathering; - bool isAuth = await this.IsResourceOwner(gathering.OrganiserId); - logger.LogInformation("isAuth: {}", isAuth); - if (!isAuth) - { - return Forbid(); - } + Gathering gathering = booking.Gathering; + bool isAuth = await this.IsResourceOwner(gathering.OrganiserId); + logger.LogInformation("isAuth: {}", isAuth); + if (!isAuth) + { + return Forbid(); + } - booking.CheckInDateTime = DateTimeOffset.UtcNow; - booking = await bookingService.UpdateBooking( - bookingId, - bookingReqDto: booking.ToBookingDto() - ); - return Ok(booking); - } + booking.CheckInDateTime = DateTimeOffset.UtcNow; + booking = await bookingService.UpdateBooking( + bookingId, + bookingReqDto: booking.ToBookingDto() + ); + return Ok(booking); + } } diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index 7965276..2681ef2 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -14,161 +14,161 @@ namespace Evently.Server.Features.Bookings.Services; public sealed class BookingService( - IMediaRenderer mediaRenderer, - IObjectStorageService objectStorageService, - IValidator validator, - IOptions settings, - AppDbContext db + IMediaRenderer mediaRenderer, + IObjectStorageService objectStorageService, + IValidator validator, + IOptions settings, + AppDbContext db ) : IBookingService { - private readonly string _containerName = settings.Value.StorageAccount.AccountName; - - public async Task GetBooking(string bookingId) - { - return await db - .Bookings.Include((b) => b.Account) - .Include((b) => b.Gathering) - .ThenInclude((g) => g!.GatheringCategoryDetails) - .ThenInclude((detail) => detail.Category) - .FirstOrDefaultAsync((be) => be.BookingId == bookingId); - } - - public async Task> GetBookings( - string? accountId, - long? gatheringId, - DateTimeOffset? checkInStart, - DateTimeOffset? checkInEnd, - DateTimeOffset? gatheringStartBefore, - DateTimeOffset? gatheringStartAfter, - DateTimeOffset? gatheringEndBefore, - DateTimeOffset? gatheringEndAfter, - bool? isCancelled, - int? offset, - int? limit - ) - { - IQueryable query = db - .Bookings.Where((b) => accountId == null || b.AttendeeId == accountId) - .Where((b) => gatheringId == null || b.GatheringId == gatheringId) - .Where((c) => checkInStart == null || checkInStart <= c.CheckInDateTime) - .Where((b) => checkInEnd == null || b.CheckInDateTime <= checkInEnd) - .Where((b) => isCancelled == null || b.CancellationDateTime.HasValue == isCancelled) - .Where( - (b) => - gatheringStartBefore == null - || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore - ) - .Where( - (b) => - gatheringStartAfter == null - || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter - ) - .Where( - (b) => - gatheringEndBefore == null - || b.Gathering != null && b.Gathering.End <= gatheringEndBefore - ) - .Where( - (b) => - gatheringEndAfter == null - || b.Gathering != null && b.Gathering.End >= gatheringEndAfter - ) - .Include((b) => b.Account) - .Include((b) => b.Gathering) - .ThenInclude((g) => g!.GatheringCategoryDetails) - .ThenInclude((detail) => detail.Category); - - int totalCount = await query.CountAsync(); - - List bookingEvents = await query - .OrderByDescending((be) => be.CreationDateTime) - .Skip(offset ?? 0) - .Take(limit ?? int.MaxValue) - .ToListAsync(); - - return new PageResult { Items = bookingEvents, TotalCount = totalCount }; - } - - public async Task CreateBooking(BookingReqDto bookingReqDto) - { - Booking booking = bookingReqDto.ToBooking(); - ValidationResult validationResult = await validator.ValidateAsync(booking); - if (!validationResult.IsValid) - { - throw new ArgumentException( - $"Account has already booked this gathering (GatheringId: {booking.GatheringId})" - ); - } - - booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}"; - await db.Bookings.AddAsync(booking); - await db.SaveChangesAsync(); - return (await GetBooking(booking.BookingId))!; - } - - public async Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto) - { - Booking booking = bookingReqDto.ToBooking(); - - ValidationResult validationResult = await validator.ValidateAsync(booking); - if (!validationResult.IsValid) - { - throw new ArgumentException( - string.Join("\n", values: validationResult.Errors.Select(e => e.ErrorMessage)) - ); - } - - Booking current = - await db.Bookings.AsTracking().FirstOrDefaultAsync((be) => be.BookingId == bookingId) - ?? throw new KeyNotFoundException($"{booking.BookingId} not found"); - - current.AttendeeId = booking.AttendeeId; - current.GatheringId = booking.GatheringId; - current.CreationDateTime = booking.CreationDateTime; - current.CheckInDateTime = booking.CheckInDateTime; - current.CheckoutDateTime = booking.CheckoutDateTime; - current.CancellationDateTime = booking.CancellationDateTime; - - await db.SaveChangesAsync(); - return (await GetBooking(booking.BookingId))!; - } - - public async Task RenderTicket(string bookingId) - { - Booking? booking = await GetBooking(bookingId); - if (booking?.Account is null || booking.Gathering is null) - { - throw new KeyNotFoundException( - $"Booking with id: {bookingId} not found or related member or gathering is null" - ); - } - - string qrData = JsonSerializer.Serialize(new { bookingEventId = bookingId }); - BinaryData binaryData = mediaRenderer.RenderQr(qrData); - string fileName = $"bookings/{bookingId}/qrcode.png"; - - Uri uri; - bool isFileExists = await objectStorageService.IsFileExists(_containerName, fileName); - if (!isFileExists) - { - uri = await objectStorageService.UploadFile( - _containerName, - fileName, - binaryData, - "image/png" - ); - } - else - { - uri = await objectStorageService.GetFileUri(_containerName, fileName); - } - - Dictionary props = new() - { - { "Booking", booking }, - { "QrCodeUrl", uri.AbsoluteUri }, - }; - - return await mediaRenderer.RenderComponentHtml(props); - } + private readonly string _containerName = settings.Value.StorageAccount.AccountName; + + public async Task GetBooking(string bookingId) + { + return await db + .Bookings.Include((b) => b.Account) + .Include((b) => b.Gathering) + .ThenInclude((g) => g!.GatheringCategoryDetails) + .ThenInclude((detail) => detail.Category) + .FirstOrDefaultAsync((be) => be.BookingId == bookingId); + } + + public async Task> GetBookings( + string? accountId, + long? gatheringId, + DateTimeOffset? checkInStart, + DateTimeOffset? checkInEnd, + DateTimeOffset? gatheringStartBefore, + DateTimeOffset? gatheringStartAfter, + DateTimeOffset? gatheringEndBefore, + DateTimeOffset? gatheringEndAfter, + bool? isCancelled, + int? offset, + int? limit + ) + { + IQueryable query = db + .Bookings.Where((b) => accountId == null || b.AttendeeId == accountId) + .Where((b) => gatheringId == null || b.GatheringId == gatheringId) + .Where((c) => checkInStart == null || checkInStart <= c.CheckInDateTime) + .Where((b) => checkInEnd == null || b.CheckInDateTime <= checkInEnd) + .Where((b) => isCancelled == null || b.CancellationDateTime.HasValue == isCancelled) + .Where( + (b) => + gatheringStartBefore == null + || b.Gathering != null && b.Gathering.Start <= gatheringStartBefore + ) + .Where( + (b) => + gatheringStartAfter == null + || b.Gathering != null && b.Gathering.Start >= gatheringStartAfter + ) + .Where( + (b) => + gatheringEndBefore == null + || b.Gathering != null && b.Gathering.End <= gatheringEndBefore + ) + .Where( + (b) => + gatheringEndAfter == null + || b.Gathering != null && b.Gathering.End >= gatheringEndAfter + ) + .Include((b) => b.Account) + .Include((b) => b.Gathering) + .ThenInclude((g) => g!.GatheringCategoryDetails) + .ThenInclude((detail) => detail.Category); + + int totalCount = await query.CountAsync(); + + List bookingEvents = await query + .OrderByDescending((be) => be.CreationDateTime) + .Skip(offset ?? 0) + .Take(limit ?? int.MaxValue) + .ToListAsync(); + + return new PageResult { Items = bookingEvents, TotalCount = totalCount }; + } + + public async Task CreateBooking(BookingReqDto bookingReqDto) + { + Booking booking = bookingReqDto.ToBooking(); + ValidationResult validationResult = await validator.ValidateAsync(booking); + if (!validationResult.IsValid) + { + throw new ArgumentException( + $"Account has already booked this gathering (GatheringId: {booking.GatheringId})" + ); + } + + booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}"; + await db.Bookings.AddAsync(booking); + await db.SaveChangesAsync(); + return (await GetBooking(booking.BookingId))!; + } + + public async Task UpdateBooking(string bookingId, BookingReqDto bookingReqDto) + { + Booking booking = bookingReqDto.ToBooking(); + + ValidationResult validationResult = await validator.ValidateAsync(booking); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join("\n", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); + } + + Booking current = + await db.Bookings.AsTracking().FirstOrDefaultAsync((be) => be.BookingId == bookingId) + ?? throw new KeyNotFoundException($"{booking.BookingId} not found"); + + current.AttendeeId = booking.AttendeeId; + current.GatheringId = booking.GatheringId; + current.CreationDateTime = booking.CreationDateTime; + current.CheckInDateTime = booking.CheckInDateTime; + current.CheckoutDateTime = booking.CheckoutDateTime; + current.CancellationDateTime = booking.CancellationDateTime; + + await db.SaveChangesAsync(); + return (await GetBooking(booking.BookingId))!; + } + + public async Task RenderTicket(string bookingId) + { + Booking? booking = await GetBooking(bookingId); + if (booking?.Account is null || booking.Gathering is null) + { + throw new KeyNotFoundException( + $"Booking with id: {bookingId} not found or related member or gathering is null" + ); + } + + string qrData = JsonSerializer.Serialize(new { bookingEventId = bookingId }); + BinaryData binaryData = mediaRenderer.RenderQr(qrData); + string fileName = $"bookings/{bookingId}/qrcode.png"; + + Uri uri; + bool isFileExists = await objectStorageService.IsFileExists(_containerName, fileName); + if (!isFileExists) + { + uri = await objectStorageService.UploadFile( + _containerName, + fileName, + binaryData, + "image/png" + ); + } + else + { + uri = await objectStorageService.GetFileUri(_containerName, fileName); + } + + Dictionary props = new() + { + { "Booking", booking }, + { "QrCodeUrl", uri.AbsoluteUri }, + }; + + return await mediaRenderer.RenderComponentHtml(props); + } } diff --git a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs index c2bf992..556f03d 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs @@ -5,11 +5,11 @@ namespace Evently.Server.Features.Bookings.Services; public sealed class BookingValidator : AbstractValidator { - public BookingValidator() - { - RuleFor((booking) => booking.GatheringId) - .NotEmpty() - .WithMessage("GatheringId is required."); - RuleFor((booking) => booking.AttendeeId).NotEmpty().WithMessage("AccountId is required."); - } + public BookingValidator() + { + RuleFor((booking) => booking.GatheringId) + .NotEmpty() + .WithMessage("GatheringId is required."); + RuleFor((booking) => booking.AttendeeId).NotEmpty().WithMessage("AccountId is required."); + } } diff --git a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs index 6756b17..e0f5bfb 100644 --- a/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs +++ b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs @@ -10,24 +10,24 @@ namespace Evently.Server.Features.Categories.Controllers; [Route("api/v1/[controller]")] public sealed class CategoriesController(ICategoryService categoryService) : ControllerBase { - [HttpGet(Name = "GetCategories")] - public async Task>> GetCategories(long? memberId, bool? approved) - { - PageResult result = await categoryService.GetCategories(memberId, approved); - List topics = result.Items; - int total = result.TotalCount; - HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append( - "X-Total-Count", - value: total.ToString(CultureInfo.InvariantCulture) - ); - return Ok(topics); - } + [HttpGet(Name = "GetCategories")] + public async Task>> GetCategories(long? memberId, bool? approved) + { + PageResult result = await categoryService.GetCategories(memberId, approved); + List topics = result.Items; + int total = result.TotalCount; + HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); + return Ok(topics); + } - [HttpPost("", Name = "CreateCategory")] - public async Task> CreateCategory(Category category) - { - category = await categoryService.CreateCategory(category); - return Ok(category); - } + [HttpPost("", Name = "CreateCategory")] + public async Task> CreateCategory(Category category) + { + category = await categoryService.CreateCategory(category); + return Ok(category); + } } diff --git a/src/Evently.Server/Features/Categories/Services/CategoryService.cs b/src/Evently.Server/Features/Categories/Services/CategoryService.cs index 4442f99..e046ef4 100644 --- a/src/Evently.Server/Features/Categories/Services/CategoryService.cs +++ b/src/Evently.Server/Features/Categories/Services/CategoryService.cs @@ -8,29 +8,29 @@ namespace Evently.Server.Features.Categories.Services; public sealed class CategoryService(AppDbContext db) : ICategoryService { - public async Task CreateCategory(Category category) - { - await db.Categories.AddAsync(category); - await db.SaveChangesAsync(); - return category; - } + public async Task CreateCategory(Category category) + { + await db.Categories.AddAsync(category); + await db.SaveChangesAsync(); + return category; + } - public async Task> GetCategories(long? gatheringId, bool? approved) - { - IQueryable query = db - .Categories.Include((category) => category.GatheringCategoryDetails) - .Where( - (category) => - gatheringId == null - || category.GatheringCategoryDetails.Any( - (detail) => detail.GatheringId == gatheringId - ) - ) - .Where((category) => approved == null || category.Approved == approved); + public async Task> GetCategories(long? gatheringId, bool? approved) + { + IQueryable query = db + .Categories.Include((category) => category.GatheringCategoryDetails) + .Where( + (category) => + gatheringId == null + || category.GatheringCategoryDetails.Any( + (detail) => detail.GatheringId == gatheringId + ) + ) + .Where((category) => approved == null || category.Approved == approved); - int totalCount = await query.CountAsync(); - List topics = await query.ToListAsync(); + int totalCount = await query.CountAsync(); + List topics = await query.ToListAsync(); - return new PageResult { Items = topics, TotalCount = totalCount }; - } + return new PageResult { Items = topics, TotalCount = totalCount }; + } } diff --git a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs index bba3c5e..930df90 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailAdapter.cs @@ -8,54 +8,54 @@ namespace Evently.Server.Features.Emails.Services; public sealed class EmailAdapter(ILogger logger, IOptions settings) - : IEmailerAdapter + : IEmailerAdapter { - private readonly string _from = settings.Value.EmailSettings.ActualFrom; - private readonly string _password = settings.Value.EmailSettings.SmtpPassword; + private readonly string _from = settings.Value.EmailSettings.ActualFrom; + private readonly string _password = settings.Value.EmailSettings.SmtpPassword; - public async Task SendEmailAsync( - string senderEmail, - string recipientEmail, - string subject, - string body - ) - { - MimeMessage emailMessage = CreateMessage(senderEmail, recipientEmail, subject, body); + public async Task SendEmailAsync( + string senderEmail, + string recipientEmail, + string subject, + string body + ) + { + MimeMessage emailMessage = CreateMessage(senderEmail, recipientEmail, subject, body); - try - { - await SendEmail(emailMessage); - } - catch (Exception ex) - { - logger.LogCallbackUrl(ex.Message); - } - } + try + { + await SendEmail(emailMessage); + } + catch (Exception ex) + { + logger.LogCallbackUrl(ex.Message); + } + } - private static MimeMessage CreateMessage( - string senderEmail, - string recipientEmail, - string subject, - string body - ) - { - MimeMessage emailMessage = new(); - emailMessage.From.Add(new MailboxAddress(senderEmail, senderEmail)); - emailMessage.To.Add(new MailboxAddress(recipientEmail, recipientEmail)); - emailMessage.Subject = subject; + private static MimeMessage CreateMessage( + string senderEmail, + string recipientEmail, + string subject, + string body + ) + { + MimeMessage emailMessage = new(); + emailMessage.From.Add(new MailboxAddress(senderEmail, senderEmail)); + emailMessage.To.Add(new MailboxAddress(recipientEmail, recipientEmail)); + emailMessage.Subject = subject; - BodyBuilder bodyBuilder = new() { HtmlBody = body }; - emailMessage.Body = bodyBuilder.ToMessageBody(); - return emailMessage; - } + BodyBuilder bodyBuilder = new() { HtmlBody = body }; + emailMessage.Body = bodyBuilder.ToMessageBody(); + return emailMessage; + } - private async Task SendEmail(MimeMessage emailMessage) - { - SmtpClient client = new(); - await client.ConnectAsync("smtp.gmail.com", port: 587, useSsl: false); - // smtp auth - await client.AuthenticateAsync(_from, _password); - await client.SendAsync(emailMessage); - await client.DisconnectAsync(true); - } + private async Task SendEmail(MimeMessage emailMessage) + { + SmtpClient client = new(); + await client.ConnectAsync("smtp.gmail.com", port: 587, useSsl: false); + // smtp auth + await client.AuthenticateAsync(_from, _password); + await client.SendAsync(emailMessage); + await client.DisconnectAsync(true); + } } diff --git a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs index 4c7b1e9..023b0a7 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs @@ -6,45 +6,45 @@ namespace Evently.Server.Features.Emails.Services; public sealed class EmailBackgroundService( - IServiceScopeFactory scopeFactory, - ChannelReader reader, - IEmailerAdapter emailerAdapter, - ILogger logger + IServiceScopeFactory scopeFactory, + ChannelReader reader, + IEmailerAdapter emailerAdapter, + ILogger logger ) : BackgroundService { - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (await reader.WaitToReadAsync(stoppingToken)) - { - try - { - string bookingId = await reader.ReadAsync(stoppingToken); + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (await reader.WaitToReadAsync(stoppingToken)) + { + try + { + string bookingId = await reader.ReadAsync(stoppingToken); - using IServiceScope scope = scopeFactory.CreateScope(); - IBookingService bookingService = - scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + IBookingService bookingService = + scope.ServiceProvider.GetRequiredService(); - Booking? booking = await bookingService.GetBooking(bookingId); - if (booking?.Account?.Email is null) - { - continue; - } + Booking? booking = await bookingService.GetBooking(bookingId); + if (booking?.Account?.Email is null) + { + continue; + } - Account account = booking.Account; + Account account = booking.Account; - string html = await bookingService.RenderTicket(bookingId); - await emailerAdapter.SendEmailAsync( - "noreply@evently", - account.Email, - "Test QR ticket", - html - ); - logger.LogSuccessEmail(account.Email); - } - catch (Exception ex) - { - logger.LogError("email error: {}", ex.Message); - } - } - } + string html = await bookingService.RenderTicket(bookingId); + await emailerAdapter.SendEmailAsync( + "noreply@evently", + account.Email, + "Test QR ticket", + html + ); + logger.LogSuccessEmail(account.Email); + } + catch (Exception ex) + { + logger.LogError("email error: {}", ex.Message); + } + } + } } diff --git a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs index 7cc17be..3f42422 100644 --- a/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs +++ b/src/Evently.Server/Features/Emails/Services/MediaRenderer.cs @@ -11,33 +11,33 @@ namespace Evently.Server.Features.Emails.Services; public sealed class MediaRenderer(BlazorHtmlRenderer htmlRenderer) : IMediaRenderer { - public async Task RenderComponentHtml(Dictionary dictionary) - where T : IComponent - { - string html = await htmlRenderer.Dispatcher.InvokeAsync(async () => - { - ParameterView parameters = ParameterView.FromDictionary(dictionary); - HtmlRootComponent output = await htmlRenderer.RenderComponentAsync(parameters); - return output.ToHtmlString(); - }); - return html; - } + public async Task RenderComponentHtml(Dictionary dictionary) + where T : IComponent + { + string html = await htmlRenderer.Dispatcher.InvokeAsync(async () => + { + ParameterView parameters = ParameterView.FromDictionary(dictionary); + HtmlRootComponent output = await htmlRenderer.RenderComponentAsync(parameters); + return output.ToHtmlString(); + }); + return html; + } - public BinaryData RenderQr(string qrData) - { - using QRCodeGenerator qrGenerator = new(); - QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrData, QRCodeGenerator.ECCLevel.Q); - PngByteQRCode qrCode = new(qrCodeData); - byte[] imageBytes = qrCode.GetGraphic(20); - return BinaryData.FromBytes(imageBytes); - } + public BinaryData RenderQr(string qrData) + { + using QRCodeGenerator qrGenerator = new(); + QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrData, QRCodeGenerator.ECCLevel.Q); + PngByteQRCode qrCode = new(qrCodeData); + byte[] imageBytes = qrCode.GetGraphic(20); + return BinaryData.FromBytes(imageBytes); + } - public BinaryData RenderPdf(string html) - { - using MemoryStream ms = new(); - using PdfDocument pdf = PdfGenerator.GeneratePdf(html, PageSize.A4); - pdf.Save(ms); - byte[] bytes = ms.ToArray(); - return BinaryData.FromBytes(bytes); - } + public BinaryData RenderPdf(string html) + { + using MemoryStream ms = new(); + using PdfDocument pdf = PdfGenerator.GeneratePdf(html, PageSize.A4); + pdf.Save(ms); + byte[] bytes = ms.ToArray(); + return BinaryData.FromBytes(bytes); + } } diff --git a/src/Evently.Server/Features/Files/Controllers/FilesController.cs b/src/Evently.Server/Features/Files/Controllers/FilesController.cs index 9dfb2b3..9a5f63d 100644 --- a/src/Evently.Server/Features/Files/Controllers/FilesController.cs +++ b/src/Evently.Server/Features/Files/Controllers/FilesController.cs @@ -8,48 +8,48 @@ namespace Evently.Server.Features.Files.Controllers; [ApiController] [Route("api/v1/[controller]")] public class FilesController( - ILogger logger, - IObjectStorageService objectStorageService + ILogger logger, + IObjectStorageService objectStorageService ) : ControllerBase { - [HttpGet("object-storage/{bucket}", Name = "GetFile")] - public async Task GetFile(string bucket, [Required] [FromQuery] string fileName) - { - logger.LogInformation("fileName: {}", fileName); - try - { - BinaryData binaryData = await objectStorageService.GetFile(bucket, fileName); - logger.LogInformation("binaryData.MediaType: {}", binaryData.MediaType); - string contentType = binaryData.MediaType ?? GetContentType(fileName); - return File( - fileContents: binaryData.ToArray(), - fileDownloadName: fileName, - contentType: contentType - ); - } - catch (Exception ex) - { - return ex switch - { - FileNotFoundException => NotFound( - $"File '{fileName}' not found in bucket '{bucket}'." - ), - _ => StatusCode( - statusCode: 500, - "An unexpected error occurred while retrieving the file." - ), - }; - } - } + [HttpGet("object-storage/{bucket}", Name = "GetFile")] + public async Task GetFile(string bucket, [Required] [FromQuery] string fileName) + { + logger.LogInformation("fileName: {}", fileName); + try + { + BinaryData binaryData = await objectStorageService.GetFile(bucket, fileName); + logger.LogInformation("binaryData.MediaType: {}", binaryData.MediaType); + string contentType = binaryData.MediaType ?? GetContentType(fileName); + return File( + fileContents: binaryData.ToArray(), + fileDownloadName: fileName, + contentType: contentType + ); + } + catch (Exception ex) + { + return ex switch + { + FileNotFoundException => NotFound( + $"File '{fileName}' not found in bucket '{bucket}'." + ), + _ => StatusCode( + statusCode: 500, + "An unexpected error occurred while retrieving the file." + ), + }; + } + } - private static string GetContentType(string fileName) - { - FileExtensionContentTypeProvider provider = new(); - if (!provider.TryGetContentType(fileName, contentType: out string? contentType)) - { - contentType = "application/octet-stream"; // Default fallback - } + private static string GetContentType(string fileName) + { + FileExtensionContentTypeProvider provider = new(); + if (!provider.TryGetContentType(fileName, contentType: out string? contentType)) + { + contentType = "application/octet-stream"; // Default fallback + } - return contentType; - } + return contentType; + } } diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index cfc940a..3ad4373 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -12,132 +12,132 @@ namespace Evently.Server.Features.Files.Services; // Based on https://tinyurl.com/5pam66xn public sealed class ObjectStorageService : IObjectStorageService { - private readonly BlobServiceClient _blobServiceClient; - private readonly ContentSafetyClient? _contentSafetyClient; - private readonly ILogger _logger; - - public ObjectStorageService(IOptions settings, ILogger logger) - { - _logger = logger; - _blobServiceClient = new BlobServiceClient( - settings.Value.StorageAccount.AzureStorageConnectionString - ); - - try - { - _contentSafetyClient = new ContentSafetyClient( - endpoint: new Uri(settings.Value.AzureAiFoundry.ContentSafetyEndpoint), - credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey) - ); - } - catch (Exception ex) - { - // silence the error - _logger.LogError( - "error creating content safety client: {message}. Content moderation skipped.", - ex.Message - ); - } - } - - public async Task UploadFile( - string containerName, - string fileName, - BinaryData binaryData, - string mimeType = "application/octet-stream" - ) - { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( - containerName - ); - await containerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer); - - BlobClient blobClient = containerClient.GetBlobClient(fileName); - - BlobUploadOptions uploadOptions = new() - { - HttpHeaders = new BlobHttpHeaders { ContentType = mimeType }, - // https://tinyurl.com/ms4hvsta - // By default, there will be condition to prevent overwrite. - // Set the conditions to null so that overwrite will be allowed. - Conditions = null, - }; - await blobClient.UploadAsync(binaryData, uploadOptions); - return blobClient.Uri; - } - - public async Task IsFileExists(string containerName, string fileName) - { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( - containerName - ); - BlobClient blobClient = containerClient.GetBlobClient(fileName); - Response result = await blobClient.ExistsAsync(); - return result.Value; - } - - public Task GetFileUri(string containerName, string fileName) - { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( - containerName - ); - BlobClient blobClient = containerClient.GetBlobClient(fileName); - return Task.FromResult(blobClient.Uri); - } - - public async Task GetFile(string containerName, string fileName) - { - BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( - containerName - ); - BlobClient blobClient = containerClient.GetBlobClient(fileName); - - Response result = await blobClient.ExistsAsync(); - if (!result.Value) - { - throw new FileNotFoundException($"File {fileName} not found"); - } - - using MemoryStream ms = new(); - try - { - await blobClient.DownloadToAsync(ms); - } - catch (Exception ex) - { - _logger.LogError("error getting file: {}", ex.Message); - } - - byte[] bytes = ms.ToArray(); - BinaryData data = BinaryData.FromBytes(bytes); - return data; - } - - public async Task PassesContentModeration(BinaryData binaryData) - { - if (_contentSafetyClient is null) - { - return true; - } - - ContentSafetyImageData image = new(binaryData); - AnalyzeImageOptions request = new(image); - Response response; - try - { - response = await _contentSafetyClient.AnalyzeImageAsync(request); - } - catch (RequestFailedException ex) - { - _logger.LogContentModerationError(ex.Status.ToString(), ex.ErrorCode ?? "", ex.Message); - throw; - } - - AnalyzeImageResult result = response.Value; - int dangerScore = result - .CategoriesAnalysis.Select(v => v.Severity ?? 0) - .DefaultIfEmpty(0) - .Aggregate((a, b) => a + b); - return dangerScore == 0; - } + private readonly BlobServiceClient _blobServiceClient; + private readonly ContentSafetyClient? _contentSafetyClient; + private readonly ILogger _logger; + + public ObjectStorageService(IOptions settings, ILogger logger) + { + _logger = logger; + _blobServiceClient = new BlobServiceClient( + settings.Value.StorageAccount.AzureStorageConnectionString + ); + + try + { + _contentSafetyClient = new ContentSafetyClient( + endpoint: new Uri(settings.Value.AzureAiFoundry.ContentSafetyEndpoint), + credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey) + ); + } + catch (Exception ex) + { + // silence the error + _logger.LogError( + "error creating content safety client: {message}. Content moderation skipped.", + ex.Message + ); + } + } + + public async Task UploadFile( + string containerName, + string fileName, + BinaryData binaryData, + string mimeType = "application/octet-stream" + ) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); + await containerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer); + + BlobClient blobClient = containerClient.GetBlobClient(fileName); + + BlobUploadOptions uploadOptions = new() + { + HttpHeaders = new BlobHttpHeaders { ContentType = mimeType }, + // https://tinyurl.com/ms4hvsta + // By default, there will be condition to prevent overwrite. + // Set the conditions to null so that overwrite will be allowed. + Conditions = null, + }; + await blobClient.UploadAsync(binaryData, uploadOptions); + return blobClient.Uri; + } + + public async Task IsFileExists(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); + BlobClient blobClient = containerClient.GetBlobClient(fileName); + Response result = await blobClient.ExistsAsync(); + return result.Value; + } + + public Task GetFileUri(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); + BlobClient blobClient = containerClient.GetBlobClient(fileName); + return Task.FromResult(blobClient.Uri); + } + + public async Task GetFile(string containerName, string fileName) + { + BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient( + containerName + ); + BlobClient blobClient = containerClient.GetBlobClient(fileName); + + Response result = await blobClient.ExistsAsync(); + if (!result.Value) + { + throw new FileNotFoundException($"File {fileName} not found"); + } + + using MemoryStream ms = new(); + try + { + await blobClient.DownloadToAsync(ms); + } + catch (Exception ex) + { + _logger.LogError("error getting file: {}", ex.Message); + } + + byte[] bytes = ms.ToArray(); + BinaryData data = BinaryData.FromBytes(bytes); + return data; + } + + public async Task PassesContentModeration(BinaryData binaryData) + { + if (_contentSafetyClient is null) + { + return true; + } + + ContentSafetyImageData image = new(binaryData); + AnalyzeImageOptions request = new(image); + Response response; + try + { + response = await _contentSafetyClient.AnalyzeImageAsync(request); + } + catch (RequestFailedException ex) + { + _logger.LogContentModerationError(ex.Status.ToString(), ex.ErrorCode ?? "", ex.Message); + throw; + } + + AnalyzeImageResult result = response.Value; + int dangerScore = result + .CategoriesAnalysis.Select(v => v.Severity ?? 0) + .DefaultIfEmpty(0) + .Aggregate((a, b) => a + b); + return dangerScore == 0; + } } diff --git a/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs index 3fb557c..b008922 100644 --- a/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs +++ b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs @@ -15,154 +15,154 @@ namespace Evently.Server.Features.Gatherings.Controllers; [ApiController] [Route("api/v1/[controller]")] public sealed class GatheringsController( - IOptions settings, - ILogger logger, - IGatheringService gatheringService, - IObjectStorageService objectStorageService + IOptions settings, + ILogger logger, + IGatheringService gatheringService, + IObjectStorageService objectStorageService ) : ControllerBase { - private readonly string _containerName = settings.Value.StorageAccount.AccountName; - - [HttpGet("{gatheringId:long}", Name = "GetGathering")] - public async Task> GetGathering(long gatheringId) - { - Gathering? customer = await gatheringService.GetGathering(gatheringId); - if (customer is null) - { - return NotFound(); - } - - return Ok(customer); - } - - [HttpGet("", Name = "GetGatherings")] - public async Task>> GetGatherings( - string? attendeeId, - string? organiserId, - string? name, - DateTimeOffset? startDateBefore, - DateTimeOffset? startDateAfter, - DateTimeOffset? endDateBefore, - DateTimeOffset? endDateAfter, - bool? isCancelled, - [FromQuery(Name = "categoryIds[]")] long[]? categoryIds, - int? offset, - int? limit - ) - { - logger.LogInformation("categoryIds: {}", string.Join(",", values: categoryIds ?? [])); - PageResult result = await gatheringService.GetGatherings( - attendeeId, - organiserId, - name, - startDateBefore, - startDateAfter, - endDateBefore, - endDateAfter, - isCancelled, - categoryIds: categoryIds?.ToHashSet(), - offset, - limit - ); - List exhibitions = result.Items; - int total = result.TotalCount; - HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); - HttpContext.Response.Headers.Append( - "X-Total-Count", - value: total.ToString(CultureInfo.InvariantCulture) - ); - return Ok(exhibitions); - } - - [HttpPost("", Name = "CreateGathering")] - public async Task> CreateGathering( - [FromForm] GatheringReqDto gatheringReqDto, - [FromForm] IFormFile? coverImg - ) - { - gatheringReqDto = gatheringReqDto with { GatheringId = 0L }; - - AuthenticateResult authenticationResult = await HttpContext.AuthenticateAsync( - IdentityConstants.ExternalScheme - ); - if (!authenticationResult.Succeeded) - { - return Unauthorized(); - } - - if (coverImg != null) - { - string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); - gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; - } - - Gathering gathering = await gatheringService.CreateGathering(gatheringReqDto); - return Ok(gathering); - } - - [HttpPut("{gatheringId:long}", Name = "UpdateGathering")] - public async Task UpdateGathering( - long gatheringId, - [FromForm] GatheringReqDto gatheringReqDto, - [FromForm] IFormFile? coverImg - ) - { - Gathering? gathering = await gatheringService.GetGathering(gatheringId); - if (gathering is null) - { - return NotFound(); - } - - if (!await this.IsResourceOwner(gathering.OrganiserId)) - { - return Forbid(); - } - - if (coverImg != null) - { - string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); - gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; - } - - gathering = await gatheringService.UpdateGathering(gatheringId, gatheringReqDto); - return Ok(gathering); - } - - [HttpDelete("{gatheringId:long}", Name = "DeleteGathering")] - public async Task> DeleteGathering(long gatheringId) - { - Gathering? exhibition = await gatheringService.GetGathering(gatheringId); - if (exhibition is null) - { - return NotFound(); - } - - if (!await this.IsResourceOwner(exhibition.OrganiserId)) - { - return Forbid(); - } - - await gatheringService.DeleteGathering(gatheringId); - return NoContent(); - } - - private async Task UploadCoverImage(long gatheringId, IFormFile coverImg) - { - string fileName = - $"gatherings/{gatheringId}/cover-image{Path.GetExtension(coverImg.FileName)}"; - BinaryData binaryData = await coverImg.ToBinaryData(); - bool isContentSafe = await objectStorageService.PassesContentModeration(binaryData); - if (!isContentSafe) - { - return string.Empty; - } - - Uri uri = await objectStorageService.UploadFile( - _containerName, - fileName, - binaryData, - mimeType: MimeTypes.GetMimeType(coverImg.FileName) - ); - return uri.AbsoluteUri; - } + private readonly string _containerName = settings.Value.StorageAccount.AccountName; + + [HttpGet("{gatheringId:long}", Name = "GetGathering")] + public async Task> GetGathering(long gatheringId) + { + Gathering? customer = await gatheringService.GetGathering(gatheringId); + if (customer is null) + { + return NotFound(); + } + + return Ok(customer); + } + + [HttpGet("", Name = "GetGatherings")] + public async Task>> GetGatherings( + string? attendeeId, + string? organiserId, + string? name, + DateTimeOffset? startDateBefore, + DateTimeOffset? startDateAfter, + DateTimeOffset? endDateBefore, + DateTimeOffset? endDateAfter, + bool? isCancelled, + [FromQuery(Name = "categoryIds[]")] long[]? categoryIds, + int? offset, + int? limit + ) + { + logger.LogInformation("categoryIds: {}", string.Join(",", values: categoryIds ?? [])); + PageResult result = await gatheringService.GetGatherings( + attendeeId, + organiserId, + name, + startDateBefore, + startDateAfter, + endDateBefore, + endDateAfter, + isCancelled, + categoryIds: categoryIds?.ToHashSet(), + offset, + limit + ); + List exhibitions = result.Items; + int total = result.TotalCount; + HttpContext.Response.Headers.Append("Access-Control-Expose-Headers", "X-Total-Count"); + HttpContext.Response.Headers.Append( + "X-Total-Count", + value: total.ToString(CultureInfo.InvariantCulture) + ); + return Ok(exhibitions); + } + + [HttpPost("", Name = "CreateGathering")] + public async Task> CreateGathering( + [FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg + ) + { + gatheringReqDto = gatheringReqDto with { GatheringId = 0L }; + + AuthenticateResult authenticationResult = await HttpContext.AuthenticateAsync( + IdentityConstants.ExternalScheme + ); + if (!authenticationResult.Succeeded) + { + return Unauthorized(); + } + + if (coverImg != null) + { + string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); + gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; + } + + Gathering gathering = await gatheringService.CreateGathering(gatheringReqDto); + return Ok(gathering); + } + + [HttpPut("{gatheringId:long}", Name = "UpdateGathering")] + public async Task UpdateGathering( + long gatheringId, + [FromForm] GatheringReqDto gatheringReqDto, + [FromForm] IFormFile? coverImg + ) + { + Gathering? gathering = await gatheringService.GetGathering(gatheringId); + if (gathering is null) + { + return NotFound(); + } + + if (!await this.IsResourceOwner(gathering.OrganiserId)) + { + return Forbid(); + } + + if (coverImg != null) + { + string uri = await UploadCoverImage(gatheringReqDto.GatheringId, coverImg); + gatheringReqDto = gatheringReqDto with { CoverSrc = uri }; + } + + gathering = await gatheringService.UpdateGathering(gatheringId, gatheringReqDto); + return Ok(gathering); + } + + [HttpDelete("{gatheringId:long}", Name = "DeleteGathering")] + public async Task> DeleteGathering(long gatheringId) + { + Gathering? exhibition = await gatheringService.GetGathering(gatheringId); + if (exhibition is null) + { + return NotFound(); + } + + if (!await this.IsResourceOwner(exhibition.OrganiserId)) + { + return Forbid(); + } + + await gatheringService.DeleteGathering(gatheringId); + return NoContent(); + } + + private async Task UploadCoverImage(long gatheringId, IFormFile coverImg) + { + string fileName = + $"gatherings/{gatheringId}/cover-image{Path.GetExtension(coverImg.FileName)}"; + BinaryData binaryData = await coverImg.ToBinaryData(); + bool isContentSafe = await objectStorageService.PassesContentModeration(binaryData); + if (!isContentSafe) + { + return string.Empty; + } + + Uri uri = await objectStorageService.UploadFile( + _containerName, + fileName, + binaryData, + mimeType: MimeTypes.GetMimeType(coverImg.FileName) + ); + return uri.AbsoluteUri; + } } diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index 24a4db4..c0969f5 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -10,124 +10,124 @@ namespace Evently.Server.Features.Gatherings.Services; public sealed class GatheringService(AppDbContext db, IValidator validator) - : IGatheringService + : IGatheringService { - public async Task GetGathering(long gatheringId) - { - return await db - .Gatherings.Include(gathering => gathering.Bookings) - .Include(gathering => gathering.GatheringCategoryDetails) - .ThenInclude(detail => detail.Category) - .FirstOrDefaultAsync((gathering) => gathering.GatheringId == gatheringId); - } + public async Task GetGathering(long gatheringId) + { + return await db + .Gatherings.Include(gathering => gathering.Bookings) + .Include(gathering => gathering.GatheringCategoryDetails) + .ThenInclude(detail => detail.Category) + .FirstOrDefaultAsync((gathering) => gathering.GatheringId == gatheringId); + } - public async Task> GetGatherings( - string? attendeeId, - string? organiserId, - string? name, - DateTimeOffset? startDateBefore, - DateTimeOffset? startDateAfter, - DateTimeOffset? endDateBefore, - DateTimeOffset? endDateAfter, - bool? isCancelled, - HashSet? categoryIds, - int? offset, - int? limit - ) - { - IQueryable query = db - .Gatherings.Where( - (gathering) => name == null || EF.Functions.Like(gathering.Name, $"%{name}%") - ) - .Where((gathering) => startDateBefore == null || gathering.Start <= startDateBefore) - .Where((gathering) => startDateAfter == null || gathering.Start >= startDateAfter) - .Where((gathering) => endDateBefore == null || gathering.End <= endDateBefore) - .Where((gathering) => endDateAfter == null || gathering.End >= endDateAfter) - .Where((gathering) => organiserId == null || gathering.OrganiserId == organiserId) - .Where(gathering => - isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled - ) - .Where( - (gathering) => - categoryIds == null - || categoryIds.Count == 0 - || gathering.GatheringCategoryDetails.Any(detail => - categoryIds.Contains(detail.CategoryId) - ) - ) - .Where( - (gathering) => - attendeeId == null - || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId) - ) - .Include(gathering => gathering.Bookings.Where((be) => be.AttendeeId == attendeeId)) - .Include(gathering => gathering.GatheringCategoryDetails) - .ThenInclude(detail => detail.Category); + public async Task> GetGatherings( + string? attendeeId, + string? organiserId, + string? name, + DateTimeOffset? startDateBefore, + DateTimeOffset? startDateAfter, + DateTimeOffset? endDateBefore, + DateTimeOffset? endDateAfter, + bool? isCancelled, + HashSet? categoryIds, + int? offset, + int? limit + ) + { + IQueryable query = db + .Gatherings.Where( + (gathering) => name == null || EF.Functions.Like(gathering.Name, $"%{name}%") + ) + .Where((gathering) => startDateBefore == null || gathering.Start <= startDateBefore) + .Where((gathering) => startDateAfter == null || gathering.Start >= startDateAfter) + .Where((gathering) => endDateBefore == null || gathering.End <= endDateBefore) + .Where((gathering) => endDateAfter == null || gathering.End >= endDateAfter) + .Where((gathering) => organiserId == null || gathering.OrganiserId == organiserId) + .Where(gathering => + isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled + ) + .Where( + (gathering) => + categoryIds == null + || categoryIds.Count == 0 + || gathering.GatheringCategoryDetails.Any(detail => + categoryIds.Contains(detail.CategoryId) + ) + ) + .Where( + (gathering) => + attendeeId == null + || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId) + ) + .Include(gathering => gathering.Bookings.Where((be) => be.AttendeeId == attendeeId)) + .Include(gathering => gathering.GatheringCategoryDetails) + .ThenInclude(detail => detail.Category); - int totalCount = await query.CountAsync(); + int totalCount = await query.CountAsync(); - List gatherings = await query - .OrderBy(gathering => gathering.Start) - .Skip(offset ?? 0) - .Take(limit ?? int.MaxValue) - .Select((gathering) => gathering) - .ToListAsync(); + List gatherings = await query + .OrderBy(gathering => gathering.Start) + .Skip(offset ?? 0) + .Take(limit ?? int.MaxValue) + .Select((gathering) => gathering) + .ToListAsync(); - return new PageResult { Items = gatherings, TotalCount = totalCount }; - } + return new PageResult { Items = gatherings, TotalCount = totalCount }; + } - public async Task CreateGathering(GatheringReqDto gatheringReqDto) - { - Gathering gathering = gatheringReqDto.ToGathering(); - ValidationResult validationResult = await validator.ValidateAsync(gathering); - if (!validationResult.IsValid) - { - throw new ArgumentException( - string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) - ); - } + public async Task CreateGathering(GatheringReqDto gatheringReqDto) + { + Gathering gathering = gatheringReqDto.ToGathering(); + ValidationResult validationResult = await validator.ValidateAsync(gathering); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); + } - db.Gatherings.Add(gathering); - await db.SaveChangesAsync(); - return gathering; - } + db.Gatherings.Add(gathering); + await db.SaveChangesAsync(); + return gathering; + } - public async Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto) - { - Gathering gathering = gatheringReqDto.ToGathering(); - ValidationResult validationResult = await validator.ValidateAsync(gathering); - if (!validationResult.IsValid) - { - throw new ArgumentException( - string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) - ); - } + public async Task UpdateGathering(long gatheringId, GatheringReqDto gatheringReqDto) + { + Gathering gathering = gatheringReqDto.ToGathering(); + ValidationResult validationResult = await validator.ValidateAsync(gathering); + if (!validationResult.IsValid) + { + throw new ArgumentException( + string.Join(", ", values: validationResult.Errors.Select(e => e.ErrorMessage)) + ); + } - Gathering current = - await db - .Gatherings.AsTracking() - .Include((g) => g.GatheringCategoryDetails) - .FirstOrDefaultAsync((ex) => ex.GatheringId == gatheringId) - ?? throw new KeyNotFoundException($"{gatheringId} not found"); + Gathering current = + await db + .Gatherings.AsTracking() + .Include((g) => g.GatheringCategoryDetails) + .FirstOrDefaultAsync((ex) => ex.GatheringId == gatheringId) + ?? throw new KeyNotFoundException($"{gatheringId} not found"); - current.Name = gathering.Name; - current.Description = gathering.Description; - current.Start = gathering.Start; - current.End = gathering.End; - current.Location = gathering.Location; - current.CoverSrc = gathering.CoverSrc; - current.GatheringCategoryDetails = gathering.GatheringCategoryDetails; + current.Name = gathering.Name; + current.Description = gathering.Description; + current.Start = gathering.Start; + current.End = gathering.End; + current.Location = gathering.Location; + current.CoverSrc = gathering.CoverSrc; + current.GatheringCategoryDetails = gathering.GatheringCategoryDetails; - await db.SaveChangesAsync(); - return current; - } + await db.SaveChangesAsync(); + return current; + } - public async Task DeleteGathering(long gatheringId) - { - Gathering gathering = await db - .Gatherings.AsTracking() - .SingleAsync((gathering) => gathering.GatheringId == gatheringId); - db.Remove(gathering); - await db.SaveChangesAsync(); - } + public async Task DeleteGathering(long gatheringId) + { + Gathering gathering = await db + .Gatherings.AsTracking() + .SingleAsync((gathering) => gathering.GatheringId == gatheringId); + db.Remove(gathering); + await db.SaveChangesAsync(); + } } diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs index 594b0ae..ec0b0ec 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringValidator.cs @@ -5,33 +5,33 @@ namespace Evently.Server.Features.Gatherings.Services; public sealed class GatheringValidator : AbstractValidator { - public GatheringValidator() - { - RuleFor((exhibition) => exhibition.Name).NotEmpty().WithMessage("Name is required."); - RuleFor((exhibition) => exhibition.Description) - .NotEmpty() - .WithMessage("Description is required."); - RuleFor((exhibition) => exhibition.Start) - .NotEmpty() - .WithMessage("Starting Date is required."); - RuleFor((exhibition) => exhibition.End).NotEmpty().WithMessage("End Date is required."); - RuleFor((exhibition) => exhibition.OrganiserId) - .NotEmpty() - .WithMessage("Event Organiser Id is required."); - RuleForEach((exhibition) => exhibition.Bookings) - .Custom( - (value, context) => - { - if (value.GatheringId == 0) - { - context.AddFailure("ExhibitionId is required."); - } + public GatheringValidator() + { + RuleFor((exhibition) => exhibition.Name).NotEmpty().WithMessage("Name is required."); + RuleFor((exhibition) => exhibition.Description) + .NotEmpty() + .WithMessage("Description is required."); + RuleFor((exhibition) => exhibition.Start) + .NotEmpty() + .WithMessage("Starting Date is required."); + RuleFor((exhibition) => exhibition.End).NotEmpty().WithMessage("End Date is required."); + RuleFor((exhibition) => exhibition.OrganiserId) + .NotEmpty() + .WithMessage("Event Organiser Id is required."); + RuleForEach((exhibition) => exhibition.Bookings) + .Custom( + (value, context) => + { + if (value.GatheringId == 0) + { + context.AddFailure("ExhibitionId is required."); + } - if (string.IsNullOrEmpty(value.BookingId)) - { - context.AddFailure("BookingEventId is required."); - } - } - ); - } + if (string.IsNullOrEmpty(value.BookingId)) + { + context.AddFailure("BookingEventId is required."); + } + } + ); + } } diff --git a/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs index cd9dbac..0932769 100644 --- a/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs +++ b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs @@ -7,29 +7,29 @@ namespace Evently.Server.Features.HealthChecks.Controllers; [Route("api/v1/[controller]")] public sealed class HealthChecksController(HealthCheckService healthCheckService) : ControllerBase { - private readonly Dictionary _statuses = new() - { - { HealthStatus.Healthy, "Healthy" }, - { HealthStatus.Unhealthy, "Unhealthy" }, - { HealthStatus.Degraded, "Degraded" }, - }; + private readonly Dictionary _statuses = new() + { + { HealthStatus.Healthy, "Healthy" }, + { HealthStatus.Unhealthy, "Unhealthy" }, + { HealthStatus.Degraded, "Degraded" }, + }; - [HttpGet(Name = "HealthCheck")] - public async Task GetHealthcheck() - { - HealthReport healthReport = await healthCheckService.CheckHealthAsync(); + [HttpGet(Name = "HealthCheck")] + public async Task GetHealthcheck() + { + HealthReport healthReport = await healthCheckService.CheckHealthAsync(); - Dictionary statuses = healthReport.Entries.ToDictionary( - keySelector: key => key.Key, - elementSelector: value => _statuses[value.Value.Status] - ); - statuses["Server"] = "Healthy"; - return Ok(statuses); - } + Dictionary statuses = healthReport.Entries.ToDictionary( + keySelector: key => key.Key, + elementSelector: value => _statuses[value.Value.Status] + ); + statuses["Server"] = "Healthy"; + return Ok(statuses); + } - [HttpGet("middlewares/error-middleware", Name = "TestErrorMiddleware")] - public Task TestErrorMiddleware() - { - throw new ArgumentException("Test Error Middleware"); - } + [HttpGet("middlewares/error-middleware", Name = "TestErrorMiddleware")] + public Task TestErrorMiddleware() + { + throw new ArgumentException("Test Error Middleware"); + } } diff --git a/src/Evently.Server/Program.cs b/src/Evently.Server/Program.cs index 0aaeac7..c4c5ff2 100644 --- a/src/Evently.Server/Program.cs +++ b/src/Evently.Server/Program.cs @@ -26,10 +26,10 @@ ConfigurationManager config = builder.Configuration; ILoggerFactory logFactory = LoggerFactory.Create( - (logBuilder) => - { - logBuilder.AddSimpleConsole((opts) => opts.ColorBehavior = LoggerColorBehavior.Disabled); - } + (logBuilder) => + { + logBuilder.AddSimpleConsole((opts) => opts.ColorBehavior = LoggerColorBehavior.Disabled); + } ); ILogger logger = logFactory.CreateLogger(); @@ -40,23 +40,23 @@ string? dbConnStr = builder.Configuration.GetConnectionString("WebApiDatabase"); logger.LogValue("dbConnStr", dbConnStr); builder.Services.AddDbContext( - (options) => - { - options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - options.UseSqlServer( - dbConnStr, - sqlServerOptionsAction: opt => - opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) - ); - } + (options) => + { + options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + options.UseSqlServer( + dbConnStr, + sqlServerOptionsAction: opt => + opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) + ); + } ); // Add services to the container. builder - .Services.AddControllersWithViews() - .AddJsonOptions( - (options) => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles - ); + .Services.AddControllersWithViews() + .AddJsonOptions( + (options) => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles + ); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); @@ -91,39 +91,39 @@ // https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/7.0/default-authentication-scheme#new-behavior // No default auth scheme is set builder - .Services.AddAuthentication() - .AddCookie() - .AddGoogle( - (options) => - { - options.ClientId = settings.Value.Authentication.Google.ClientId; - options.ClientSecret = settings.Value.Authentication.Google.ClientSecret; - options.CallbackPath = "/api/signin-google"; // rmb to resister in the Google oauth dashboard - options.SignInScheme = IdentityConstants.ExternalScheme; // important to default to external scheme - https://stackoverflow.com/a/78674926/6514532 - - // Enable refresh token - options.SaveTokens = true; - options.AccessType = "offline"; - - // For debugging purpose - options.Events.OnRedirectToAuthorizationEndpoint = (context) => - { - logger.LogInformation( - "Request Path: {Request}", - context.Request.RootUri().AbsoluteUri - ); - context.HttpContext.Response.Redirect(context.RedirectUri); - return Task.CompletedTask; - }; - } - ); + .Services.AddAuthentication() + .AddCookie() + .AddGoogle( + (options) => + { + options.ClientId = settings.Value.Authentication.Google.ClientId; + options.ClientSecret = settings.Value.Authentication.Google.ClientSecret; + options.CallbackPath = "/api/signin-google"; // rmb to resister in the Google oauth dashboard + options.SignInScheme = IdentityConstants.ExternalScheme; // important to default to external scheme - https://stackoverflow.com/a/78674926/6514532 + + // Enable refresh token + options.SaveTokens = true; + options.AccessType = "offline"; + + // For debugging purpose + options.Events.OnRedirectToAuthorizationEndpoint = (context) => + { + logger.LogInformation( + "Request Path: {Request}", + context.Request.RootUri().AbsoluteUri + ); + context.HttpContext.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + } + ); builder - .Services.AddAuthorizationBuilder() - .AddPolicy( - SameAccountRequirement.PolicyName, - configurePolicy: (policy) => policy.Requirements.Add(new SameAccountRequirement()) - ); + .Services.AddAuthorizationBuilder() + .AddPolicy( + SameAccountRequirement.PolicyName, + configurePolicy: (policy) => policy.Requirements.Add(new SameAccountRequirement()) + ); // Add razor pages support to render Blazor files builder.Services.AddRazorComponents().AddInteractiveServerComponents(); @@ -134,8 +134,8 @@ using (IServiceScope serviceScope = app.Services.CreateScope()) { - AppDbContext dbContext = serviceScope.ServiceProvider.GetRequiredService(); - await dbContext.Database.MigrateAsync(); + AppDbContext dbContext = serviceScope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(); } // Use the global exception handler @@ -151,7 +151,7 @@ // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.MapOpenApi(); + app.MapOpenApi(); } app.UseHttpsRedirection(); From 3914fe5ceab40416554bddac2abe16b2ea78739e Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:25:26 +0800 Subject: [PATCH 07/10] fix: sln --- Evently.slnx | 20 ++++---- Makefile | 2 +- src/Evently.Server/Evently.Server.csproj | 51 ++++++++----------- .../src/routes/gatherings/index.tsx | 20 ++++---- .../Evently.Server.Test.csproj | 18 +++---- 5 files changed, 51 insertions(+), 60 deletions(-) diff --git a/Evently.slnx b/Evently.slnx index 438e79d..32d0618 100644 --- a/Evently.slnx +++ b/Evently.slnx @@ -1,22 +1,22 @@ - + - - - - - - + + + + + - + + - + - + \ No newline at end of file diff --git a/Makefile b/Makefile index f142e41..bd18157 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ remove-migration: fmt: dotnet tool restore - jb cleanupcode ./src/Evently.Server/**/* + cd src/Evently.Server && dotnet csharpier format . cd src/evently.client && npm run fmt docker-build-no-cache: diff --git a/src/Evently.Server/Evently.Server.csproj b/src/Evently.Server/Evently.Server.csproj index d5a4f6f..c14c46c 100644 --- a/src/Evently.Server/Evently.Server.csproj +++ b/src/Evently.Server/Evently.Server.csproj @@ -1,6 +1,6 @@ - + - net9.0 + net10.0 enable enable ee94e8bf-92f6-4422-b368-12a6d0a8704d @@ -12,43 +12,34 @@ - - + + - - + + - - + + - - + + - 9.*-* + 10.0.0 - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - - + + + + diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index bd87e3a..5a2517a 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -12,16 +12,16 @@ export const Route = createFileRoute("/gatherings/")({ component: GatheringsPage, loader: async () => { let categories: Category[] = []; - let attempts = 2; - while (attempts > 0) { - try { - categories = await getCategories(); - break; - } catch (error) { - attempts -= 1; - console.error(error); - } - } + let attempts = 2; + while (attempts > 0) { + try { + categories = await getCategories(); + break; + } catch (error) { + attempts -= 1; + console.error(error); + } + } return { categories }; }, pendingComponent: () => ( diff --git a/tests/Evently.Server.Test/Evently.Server.Test.csproj b/tests/Evently.Server.Test/Evently.Server.Test.csproj index 266df90..f41ff58 100644 --- a/tests/Evently.Server.Test/Evently.Server.Test.csproj +++ b/tests/Evently.Server.Test/Evently.Server.Test.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable false @@ -12,23 +12,23 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From ba3710bf19e71e85918d3a4e6659679fb6d11787 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 16:26:30 +0800 Subject: [PATCH 08/10] fix: immage --- src/Evently.Server/Dockerfile | 4 ++-- src/evently.client/src/domains/interfaces/route-context.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Evently.Server/Dockerfile b/src/Evently.Server/Dockerfile index 5cb75f5..4a84dd2 100644 --- a/src/Evently.Server/Dockerfile +++ b/src/Evently.Server/Dockerfile @@ -1,6 +1,6 @@ # See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. # https://learn.microsoft.com/en-us/dotnet/core/docker/build-container?tabs=windows&pivots=dotnet-9-0#create-the-dockerfile -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build RUN apt-get update RUN apt-get install curl --yes RUN curl -sL https://deb.nodesource.com/setup_20.x | bash @@ -19,7 +19,7 @@ RUN ls RUN dotnet publish ./src/Evently.Server/Evently.Server.csproj -c Release -o publish -p:UseAppHost=false # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS run +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS run WORKDIR /App/server COPY --from=build /App/publish . diff --git a/src/evently.client/src/domains/interfaces/route-context.ts b/src/evently.client/src/domains/interfaces/route-context.ts index e8c5610..0af6b5f 100644 --- a/src/evently.client/src/domains/interfaces/route-context.ts +++ b/src/evently.client/src/domains/interfaces/route-context.ts @@ -1,4 +1,4 @@ -import { Account } from "~/lib/domains/entities"; +import { Account } from "~/domains/entities"; export interface RouteContext { // The ReturnType of your useAuth hook or the value of your AuthContext From 9927ec0175fc6a805c2a5d6139555c07c501e59d Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 19:26:58 +0800 Subject: [PATCH 09/10] fix: test --- dotnet-tools.json | 13 + src/Evently.Server/Evently.Server.csproj | 1 + .../Common/Extensions/MapperExtensionTests.cs | 249 ++++---- .../Common/Setup/DatabaseFixture.cs | 29 + .../Evently.Server.Test.csproj | 56 +- .../Bookings/Services/BookingServiceTests.cs | 103 ++-- .../Services/GatheringServiceTests.cs | 562 +++++++++--------- 7 files changed, 555 insertions(+), 458 deletions(-) create mode 100644 dotnet-tools.json create mode 100644 tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..03df232 --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "1.2.1", + "commands": [ + "csharpier" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/Evently.Server/Evently.Server.csproj b/src/Evently.Server/Evently.Server.csproj index c14c46c..55a151b 100644 --- a/src/Evently.Server/Evently.Server.csproj +++ b/src/Evently.Server/Evently.Server.csproj @@ -40,6 +40,7 @@ + diff --git a/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs b/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs index c6de26a..87070f8 100644 --- a/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs +++ b/tests/Evently.Server.Test/Common/Extensions/MapperExtensionTests.cs @@ -4,123 +4,132 @@ namespace Evently.Server.Test.Common.Extensions; -public class MapperExtensionTests { - [Fact] - public void TestMapToGathering() { - // Arrange (mock values) - DateTimeOffset start = DateTimeOffset.UtcNow.AddDays(3); - DateTimeOffset end = start.AddHours(2); - - GatheringReqDto dto = new( - GatheringId: 0, - "Mock Gathering", - "Mock Description", - start, - end, - CancellationDateTime: null, - "Mock Location", - "organizer-mock", - "mock-cover.jpg", - GatheringCategoryDetails: [] - ); - - // Act - Gathering entity = dto.ToGathering(); - - // Assert - Assert.NotNull(entity); - Assert.Equal(dto.Name, entity.Name); - Assert.Equal(dto.Description, entity.Description); - Assert.Equal(dto.Start, entity.Start); - Assert.Equal(dto.End, entity.End); - Assert.Equal(dto.CancellationDateTime, entity.CancellationDateTime); - Assert.Equal(dto.Location, entity.Location); - Assert.Equal(dto.OrganiserId, entity.OrganiserId); - Assert.Equal(dto.CoverSrc, entity.CoverSrc); - Assert.NotNull(entity.GatheringCategoryDetails); - Assert.Empty(entity.GatheringCategoryDetails); - } - - - [Fact] - public void TestMapToBooking() { - // Arrange (mock values) - const string attendeeId = "attendee-mock"; - const string bookingId = "book_mock"; - const long gatheringId = 42L; - - DateTimeOffset creation = DateTimeOffset.UtcNow.AddDays(1); - DateTimeOffset checkIn = creation.AddHours(1); - DateTimeOffset checkout = creation.AddHours(2); - DateTimeOffset cancellation = creation.AddHours(3); - - // Create DTO with mock values - BookingReqDto dto = new(attendeeId, bookingId, gatheringId, creation, checkIn, checkout, cancellation); - - // Act - Booking booking = dto.ToBooking(); - - // Assert: direct field comparisons (no reflection) - Assert.NotNull(booking); - Assert.Equal(dto.AttendeeId, booking.AttendeeId); - Assert.Equal(dto.GatheringId, booking.GatheringId); - Assert.Equal(dto.CreationDateTime, booking.CreationDateTime); - Assert.Equal(dto.CheckInDateTime, booking.CheckInDateTime); - Assert.Equal(dto.CheckoutDateTime, booking.CheckoutDateTime); - Assert.Equal(dto.CancellationDateTime, booking.CancellationDateTime); - - // If BookingId is part of the mapping, also validate it: - // Assert.Equal(dto.BookingId, booking.BookingId); - } - - [Fact] - public void TestMapToBookingDto() { - // Arrange (mock values) - const string attendeeId = "attendee-mock"; - const long gatheringId = 7L; - - DateTimeOffset creation = DateTimeOffset.UtcNow.AddDays(2); - DateTimeOffset checkIn = creation.AddHours(1); - DateTimeOffset checkout = creation.AddHours(2); - DateTimeOffset? cancellation = null; - - Booking booking = new() { - AttendeeId = attendeeId, - GatheringId = gatheringId, - CreationDateTime = creation, - CheckInDateTime = checkIn, - CheckoutDateTime = checkout, - CancellationDateTime = cancellation, - }; - - // Act - BookingReqDto dto = booking.ToBookingDto(); - - // Assert: direct field comparisons (no reflection) - Assert.NotNull(dto); - Assert.Equal(booking.AttendeeId, dto.AttendeeId); - Assert.Equal(booking.GatheringId, dto.GatheringId); - Assert.Equal(booking.CreationDateTime, dto.CreationDateTime); - Assert.Equal(booking.CheckInDateTime, dto.CheckInDateTime); - Assert.Equal(booking.CheckoutDateTime, dto.CheckoutDateTime); - Assert.Equal(booking.CancellationDateTime, dto.CancellationDateTime); - } - - - [Fact] - public void TestMapToAccountDto() { - // Arrange (mock values) - Account account = new() { - Id = "acc_mock", - Email = "mock@example.com", - }; - - // Act - AccountDto accountDto = account.ToAccountDto(); - - // Assert: direct field comparisons (no reflection) - Assert.NotNull(accountDto); - Assert.Equal(account.Id, accountDto.Id); - Assert.Equal(account.Email, accountDto.Email); - } -} \ No newline at end of file +public class MapperExtensionTests +{ + [Fact] + public void TestMapToGathering() + { + // Arrange (mock values) + DateTimeOffset start = DateTimeOffset.UtcNow.AddDays(3); + DateTimeOffset end = start.AddHours(2); + + GatheringReqDto dto = new( + GatheringId: 0, + "Mock Gathering", + "Mock Description", + start, + end, + CancellationDateTime: null, + "Mock Location", + "organizer-mock", + "mock-cover.jpg", + GatheringCategoryDetails: [] + ); + + // Act + Gathering entity = dto.ToGathering(); + + // Assert + Assert.NotNull(entity); + Assert.Equal(dto.Name, entity.Name); + Assert.Equal(dto.Description, entity.Description); + Assert.Equal(dto.Start, entity.Start); + Assert.Equal(dto.End, entity.End); + Assert.Equal(dto.CancellationDateTime, entity.CancellationDateTime); + Assert.Equal(dto.Location, entity.Location); + Assert.Equal(dto.OrganiserId, entity.OrganiserId); + Assert.Equal(dto.CoverSrc, entity.CoverSrc); + Assert.NotNull(entity.GatheringCategoryDetails); + Assert.Empty(entity.GatheringCategoryDetails); + } + + [Fact] + public void TestMapToBooking() + { + // Arrange (mock values) + const string attendeeId = "attendee-mock"; + const string bookingId = "book_mock"; + const long gatheringId = 42L; + + DateTimeOffset creation = DateTimeOffset.UtcNow.AddDays(1); + DateTimeOffset checkIn = creation.AddHours(1); + DateTimeOffset checkout = creation.AddHours(2); + DateTimeOffset cancellation = creation.AddHours(3); + + // Create DTO with mock values + BookingReqDto dto = new( + attendeeId, + bookingId, + gatheringId, + creation, + checkIn, + checkout, + cancellation + ); + + // Act + Booking booking = dto.ToBooking(); + + // Assert: direct field comparisons (no reflection) + Assert.NotNull(booking); + Assert.Equal(dto.AttendeeId, booking.AttendeeId); + Assert.Equal(dto.GatheringId, booking.GatheringId); + Assert.Equal(dto.CreationDateTime, booking.CreationDateTime); + Assert.Equal(dto.CheckInDateTime, booking.CheckInDateTime); + Assert.Equal(dto.CheckoutDateTime, booking.CheckoutDateTime); + Assert.Equal(dto.CancellationDateTime, booking.CancellationDateTime); + + // If BookingId is part of the mapping, also validate it: + // Assert.Equal(dto.BookingId, booking.BookingId); + } + + [Fact] + public void TestMapToBookingDto() + { + // Arrange (mock values) + const string attendeeId = "attendee-mock"; + const long gatheringId = 7L; + + DateTimeOffset creation = DateTimeOffset.UtcNow.AddDays(2); + DateTimeOffset checkIn = creation.AddHours(1); + DateTimeOffset checkout = creation.AddHours(2); + DateTimeOffset? cancellation = null; + + Booking booking = new() + { + AttendeeId = attendeeId, + GatheringId = gatheringId, + CreationDateTime = creation, + CheckInDateTime = checkIn, + CheckoutDateTime = checkout, + CancellationDateTime = cancellation, + }; + + // Act + BookingReqDto dto = booking.ToBookingDto(); + + // Assert: direct field comparisons (no reflection) + Assert.NotNull(dto); + Assert.Equal(booking.AttendeeId, dto.AttendeeId); + Assert.Equal(booking.GatheringId, dto.GatheringId); + Assert.Equal(booking.CreationDateTime, dto.CreationDateTime); + Assert.Equal(booking.CheckInDateTime, dto.CheckInDateTime); + Assert.Equal(booking.CheckoutDateTime, dto.CheckoutDateTime); + Assert.Equal(booking.CancellationDateTime, dto.CancellationDateTime); + } + + [Fact] + public void TestMapToAccountDto() + { + // Arrange (mock values) + Account account = new() { Id = "acc_mock", Email = "mock@example.com" }; + + // Act + AccountDto accountDto = account.ToAccountDto(); + + // Assert: direct field comparisons (no reflection) + Assert.NotNull(accountDto); + Assert.Equal(account.Id, accountDto.Id); + Assert.Equal(account.Email, accountDto.Email); + } +} diff --git a/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs b/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs new file mode 100644 index 0000000..caba54e --- /dev/null +++ b/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs @@ -0,0 +1,29 @@ +using Evently.Server.Common.Data; +using Microsoft.EntityFrameworkCore; +using Testcontainers.MsSql; + +namespace Evently.Server.Test.Common.Setup; + +public class DatabaseFixture : IDisposable { + private readonly MsSqlContainer _container = new MsSqlBuilder().Build(); + private AppDbContext? _dbContext; + + public async Task GetDbContext() { + // if (_container.State == TestcontainersStates.Created) { + // return _dbContext!; + // } + await _container.StartAsync(); + string connString = _container.GetConnectionString(); + DbContextOptions contextOptions = new DbContextOptionsBuilder() + .UseSqlServer(connString) + .Options; + _dbContext = new AppDbContext(contextOptions); + await _dbContext.Database.EnsureCreatedAsync(); + return _dbContext; + } + + public void Dispose() { + _dbContext?.Dispose(); + _container.DisposeAsync(); + } +} diff --git a/tests/Evently.Server.Test/Evently.Server.Test.csproj b/tests/Evently.Server.Test/Evently.Server.Test.csproj index f41ff58..1609990 100644 --- a/tests/Evently.Server.Test/Evently.Server.Test.csproj +++ b/tests/Evently.Server.Test/Evently.Server.Test.csproj @@ -1,34 +1,32 @@  + + net10.0 + enable + enable + false + - - net10.0 - enable - enable - false - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - + + + + + + diff --git a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs index 39df43b..aa1d3ce 100644 --- a/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Bookings/Services/BookingServiceTests.cs @@ -3,51 +3,29 @@ using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; using Evently.Server.Features.Bookings.Services; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; +using Evently.Server.Test.Common.Setup; using Microsoft.Extensions.Options; using Moq; namespace Evently.Server.Test.Features.Bookings.Services; -public class BookingServiceTests : IDisposable { - private readonly IBookingService _bookingService; - private readonly SqliteConnection _conn; - private readonly AppDbContext _dbContext; +public class BookingServiceTests(DatabaseFixture dbFixture) : IClassFixture { + private readonly Mock _fileStorageServiceMock = new(); - public BookingServiceTests() { - _conn = new SqliteConnection("Filename=:memory:"); - _conn.Open(); - - // These options will be used by the context instances in this test suite, including the connection opened above. - DbContextOptions contextOptions = new DbContextOptionsBuilder() - .UseSqlite(_conn) - .Options; - - // Create the schema and seed some data - AppDbContext dbContext = new(contextOptions); - - dbContext.Database.EnsureCreated(); - _dbContext = dbContext; - - Mock mediaRendererMock = new(); - Mock fileStorageServiceMock = new(); - IOptions options = Options.Create(new Settings()); - - _bookingService = new BookingService(mediaRendererMock.Object, - fileStorageServiceMock.Object, - validator: new BookingValidator(), - options, - _dbContext); - } - - public void Dispose() { - _dbContext.Dispose(); - _conn.Dispose(); - } + private readonly Mock _mediaRendererMock = new(); + private readonly IOptions _options = Options.Create(new Settings()); [Fact] public async Task CreateBooking_WithValidData_ShouldCreateBooking() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + BookingService bookingService = new( + _mediaRendererMock.Object, + _fileStorageServiceMock.Object, + validator: new BookingValidator(), + _options, + dbContext + ); + DateTimeOffset now = DateTimeOffset.Now; // Arrange BookingReqDto bookingReqDto = new( @@ -61,7 +39,7 @@ public async Task CreateBooking_WithValidData_ShouldCreateBooking() { ); // Act - Booking result = await _bookingService.CreateBooking(bookingReqDto); + Booking result = await bookingService.CreateBooking(bookingReqDto); // Assert Assert.NotNull(result); @@ -76,6 +54,15 @@ public async Task CreateBooking_WithValidData_ShouldCreateBooking() { [Fact] public async Task CreateBooking_WithEmptyAttendeeId_ShouldThrowException() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + BookingService bookingService = new( + _mediaRendererMock.Object, + _fileStorageServiceMock.Object, + validator: new BookingValidator(), + _options, + dbContext + ); + DateTimeOffset now = DateTimeOffset.Now; // Arrange BookingReqDto invalidBookingReqDto = new( @@ -89,13 +76,24 @@ public async Task CreateBooking_WithEmptyAttendeeId_ShouldThrowException() { ); // Act & Assert - await Assert.ThrowsAsync(() => _bookingService.CreateBooking(invalidBookingReqDto)); + await Assert.ThrowsAsync(() => + bookingService.CreateBooking(invalidBookingReqDto) + ); } [Fact] public async Task GetBooking_WithValidBookingId_ShouldReturnBooking() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + BookingService bookingService = new( + _mediaRendererMock.Object, + _fileStorageServiceMock.Object, + validator: new BookingValidator(), + _options, + dbContext + ); + // Act - Booking? result = await _bookingService.GetBooking("book_abc123456"); + Booking? result = await bookingService.GetBooking("book_abc123456"); // Assert Assert.NotNull(result); @@ -104,6 +102,15 @@ public async Task GetBooking_WithValidBookingId_ShouldReturnBooking() { [Fact] public async Task UpdateBooking_WithNonExistentBookingId_ShouldThrowKeyNotFoundException() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + BookingService bookingService = new( + _mediaRendererMock.Object, + _fileStorageServiceMock.Object, + validator: new BookingValidator(), + _options, + dbContext + ); + // Arrange const string nonExistentBookingId = "book_nonexistent"; BookingReqDto updateRequest = new( @@ -118,14 +125,24 @@ public async Task UpdateBooking_WithNonExistentBookingId_ShouldThrowKeyNotFoundE // Act & Assert await Assert.ThrowsAsync(() => - _bookingService.UpdateBooking(nonExistentBookingId, updateRequest)); + bookingService.UpdateBooking(nonExistentBookingId, updateRequest) + ); } [Fact] public async Task UpdateBooking_WithCancellation_ShouldUpdateCancellationDateTime() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + BookingService bookingService = new( + _mediaRendererMock.Object, + _fileStorageServiceMock.Object, + validator: new BookingValidator(), + _options, + dbContext + ); + // Arrange DateTimeOffset cancellationTime = DateTimeOffset.Now.AddMinutes(30); - Booking? booking = await _bookingService.GetBooking("book_abc123456"); + Booking? booking = await bookingService.GetBooking("book_abc123456"); Assert.NotNull(booking); BookingReqDto updateRequest = new( @@ -139,10 +156,10 @@ public async Task UpdateBooking_WithCancellation_ShouldUpdateCancellationDateTim ); // Act - booking = await _bookingService.UpdateBooking("book_abc123456", updateRequest); + booking = await bookingService.UpdateBooking("book_abc123456", updateRequest); // Assert Assert.NotNull(booking); Assert.Equal(cancellationTime, booking.CancellationDateTime); } -} \ No newline at end of file +} diff --git a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs index f994f3e..3785417 100644 --- a/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs +++ b/tests/Evently.Server.Test/Features/Gatherings/Services/GatheringServiceTests.cs @@ -3,274 +3,304 @@ using Evently.Server.Domains.Interfaces; using Evently.Server.Domains.Models; using Evently.Server.Features.Gatherings.Services; +using Evently.Server.Test.Common.Setup; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace Evently.Server.Test.Features.Gatherings.Services; -public class GatheringServiceTests : IDisposable { - private readonly SqliteConnection _conn; - private readonly AppDbContext _dbContext; - private readonly IGatheringService _gatheringService; - - public GatheringServiceTests() { - _conn = new SqliteConnection("Filename=:memory:"); - _conn.Open(); - - // These options will be used by the context instances in this test suite, including the connection opened above. - DbContextOptions contextOptions = new DbContextOptionsBuilder() - .UseSqlite(_conn) - .Options; - - // Create the schema and seed some data - AppDbContext dbContext = new(contextOptions); - - dbContext.Database.EnsureCreated(); - _dbContext = dbContext; - - _gatheringService = new GatheringService(_dbContext, validator: new GatheringValidator()); - } - - public void Dispose() { - _dbContext.Dispose(); - _conn.Dispose(); - } - - [Fact] - public async Task CreateGathering_WithValidData_ShouldCreateGathering() { - // Arrange - GatheringReqDto gatheringReqDto = new( - GatheringId: 0, - "Test Gathering", - "Test Description", - Start: DateTimeOffset.UtcNow.AddDays(1), - End: DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - CancellationDateTime: null, - "Test Location", - "organizer123", - "test-cover.jpg", - GatheringCategoryDetails: [] - ); - - // Act - Gathering result = await _gatheringService.CreateGathering(gatheringReqDto); - - // Assert - Assert.NotNull(result); - Assert.Equal(gatheringReqDto.Name, result.Name); - Assert.Equal(gatheringReqDto.Description, result.Description); - Assert.Equal(gatheringReqDto.Start, result.Start); - Assert.Equal(gatheringReqDto.End, result.End); - Assert.Equal(gatheringReqDto.Location, result.Location); - Assert.Equal(gatheringReqDto.OrganiserId, result.OrganiserId); - - // Verify it was saved to database - Gathering? savedGathering = await _dbContext.Gatherings.FirstOrDefaultAsync(g => g.GatheringId == result.GatheringId); - Assert.NotNull(savedGathering); - } - - [Fact] - public async Task CreateGathering_WithInvalidData_ShouldThrowArgumentException() { - // Arrange - GatheringReqDto invalidGatheringReqDto = new( - GatheringId: 0, - "", // Invalid empty name - "Test Description", - Start: DateTimeOffset.UtcNow.AddDays(1), - End: DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - CancellationDateTime: null, - "Test Location", - "organizer123", - CoverSrc: null, - GatheringCategoryDetails: [] - ); - - // Act & Assert - await Assert.ThrowsAsync(() => _gatheringService.CreateGathering(invalidGatheringReqDto)); - } - - [Fact] - public async Task GetGathering_WithExistingId_ShouldReturnGathering() { - // Arrange - Gathering gathering = new() { - Name = "Test Gathering", - Description = "Test Description", - Start = DateTimeOffset.UtcNow.AddDays(1), - End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - Location = "Test Location", - OrganiserId = "organizer123", - Bookings = [], - GatheringCategoryDetails = [], - }; - - _dbContext.Gatherings.Add(gathering); - await _dbContext.SaveChangesAsync(); - - // Act - Gathering? result = await _gatheringService.GetGathering(gathering.GatheringId); - - // Assert - Assert.NotNull(result); - Assert.Equal(gathering.GatheringId, result.GatheringId); - Assert.Equal(gathering.Name, result.Name); - Assert.Equal(gathering.Description, result.Description); - } - - [Fact] - public async Task GetGathering_WithNonExistentId_ShouldReturnNull() { - // Arrange - const long nonExistentId = 999; - - // Act - Gathering? result = await _gatheringService.GetGathering(nonExistentId); - - // Assert - Assert.Null(result); - } - - [Fact] - public async Task GetGatherings_WithNameFilter_ShouldReturnFilteredResults() { - // Arrange - List gatherings = [ - new() { - Name = "XYZ Conference", - Description = "Description 1", - Start = DateTimeOffset.UtcNow.AddDays(1), - End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - Location = "Location 1", - OrganiserId = "organizer1", - Bookings = [], - GatheringCategoryDetails = [], - }, - - new() { - Name = "Art Workshop", - Description = "Description 2", - Start = DateTimeOffset.UtcNow.AddDays(2), - End = DateTimeOffset.UtcNow.AddDays(2).AddHours(2), - Location = "Location 2", - OrganiserId = "organizer2", - Bookings = [], - GatheringCategoryDetails = [], - }, - - ]; - - _dbContext.Gatherings.AddRange(gatherings); - await _dbContext.SaveChangesAsync(); - - // Act - PageResult result = await _gatheringService.GetGatherings(attendeeId: null, - organiserId: null, - "XYZ", - startDateBefore: null, - startDateAfter: null, - endDateBefore: null, - endDateAfter: null, - isCancelled: null, - categoryIds: [], - offset: null, - limit: null); - - // Assert - Assert.NotNull(result); - Assert.Equal(expected: 1, result.TotalCount); - Assert.Equal("XYZ Conference", result.Items.First().Name); - } - - [Fact] - public async Task UpdateGathering_WithValidData_ShouldUpdateGathering() { - // Arrange - Gathering gathering = new() { - Name = "Original Name", - Description = "Original Description", - Start = DateTimeOffset.UtcNow.AddDays(1), - End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - Location = "Original Location", - OrganiserId = "organizer123", - GatheringCategoryDetails = [], - }; - - _dbContext.Gatherings.Add(gathering); - await _dbContext.SaveChangesAsync(); - - GatheringReqDto updateDto = new( - gathering.GatheringId, - "Updated Name", - "Updated Description", - Start: DateTimeOffset.UtcNow.AddDays(2), - End: DateTimeOffset.UtcNow.AddDays(2).AddHours(3), - CancellationDateTime: null, - "Updated Location", - "organizer123", - "updated-cover.jpg", - GatheringCategoryDetails: [] - ); - - // Act - Gathering result = await _gatheringService.UpdateGathering(gathering.GatheringId, updateDto); - - // Assert - Assert.NotNull(result); - Assert.Equal("Updated Name", result.Name); - Assert.Equal("Updated Description", result.Description); - Assert.Equal(updateDto.Start, result.Start); - Assert.Equal(updateDto.End, result.End); - Assert.Equal("Updated Location", result.Location); - Assert.Equal("updated-cover.jpg", result.CoverSrc); - } - - [Fact] - public async Task UpdateGathering_WithNonExistentId_ShouldThrowKeyNotFoundException() { - // Arrange - GatheringReqDto updateDto = new( - GatheringId: 999, - "Updated Name", - "Updated Description", - Start: DateTimeOffset.UtcNow.AddDays(2), - End: DateTimeOffset.UtcNow.AddDays(2).AddHours(3), - CancellationDateTime: null, - "Updated Location", - "organizer123", - CoverSrc: null, - GatheringCategoryDetails: [] - ); - - // Act & Assert - await Assert.ThrowsAsync(() => _gatheringService.UpdateGathering(gatheringId: 999, updateDto)); - } - - [Fact] - public async Task DeleteGathering_WithExistingId_ShouldDeleteGathering() { - // Arrange - Gathering gathering = new() { - Name = "Test Gathering", - Description = "Test Description", - Start = DateTimeOffset.UtcNow.AddDays(1), - End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), - Location = "Test Location", - OrganiserId = "organizer123", - GatheringCategoryDetails = [], - }; - - _dbContext.Gatherings.Add(gathering); - await _dbContext.SaveChangesAsync(); - long gatheringId = gathering.GatheringId; - - // Act - await _gatheringService.DeleteGathering(gatheringId); - - // Assert - Gathering? deletedGathering = await _dbContext.Gatherings.FirstOrDefaultAsync(g => g.GatheringId == gatheringId); - Assert.Null(deletedGathering); - } - - [Fact] - public async Task DeleteGathering_WithNonExistentId_ShouldThrowInvalidOperationException() { - // Arrange - const long nonExistentId = 999; - - // Act & Assert - await Assert.ThrowsAsync(() => _gatheringService.DeleteGathering(nonExistentId)); - } -} \ No newline at end of file +public class GatheringServiceTests(DatabaseFixture dbFixture) : IClassFixture +{ + + [Fact] + public async Task CreateGathering_WithValidData_ShouldCreateGathering() { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + GatheringReqDto gatheringReqDto = new( + GatheringId: 0, + "Test Gathering", + "Test Description", + Start: DateTimeOffset.UtcNow.AddDays(1), + End: DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + CancellationDateTime: null, + "Test Location", + "organizer123", + "test-cover.jpg", + GatheringCategoryDetails: [] + ); + + // Act + Gathering result = await gatheringService.CreateGathering(gatheringReqDto); + + // Assert + Assert.NotNull(result); + Assert.Equal(gatheringReqDto.Name, result.Name); + Assert.Equal(gatheringReqDto.Description, result.Description); + Assert.Equal(gatheringReqDto.Start, result.Start); + Assert.Equal(gatheringReqDto.End, result.End); + Assert.Equal(gatheringReqDto.Location, result.Location); + Assert.Equal(gatheringReqDto.OrganiserId, result.OrganiserId); + + // Verify it was saved to database + Gathering? savedGathering = await dbContext.Gatherings.FirstOrDefaultAsync(g => + g.GatheringId == result.GatheringId + ); + Assert.NotNull(savedGathering); + } + + [Fact] + public async Task CreateGathering_WithInvalidData_ShouldThrowArgumentException() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + GatheringReqDto invalidGatheringReqDto = new( + GatheringId: 0, + "", // Invalid empty name + "Test Description", + Start: DateTimeOffset.UtcNow.AddDays(1), + End: DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + CancellationDateTime: null, + "Test Location", + "organizer123", + CoverSrc: null, + GatheringCategoryDetails: [] + ); + + // Act & Assert + await Assert.ThrowsAsync(() => + gatheringService.CreateGathering(invalidGatheringReqDto) + ); + } + + [Fact] + public async Task GetGathering_WithExistingId_ShouldReturnGathering() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + Gathering gathering = new() + { + Name = "Test Gathering", + Description = "Test Description", + Start = DateTimeOffset.UtcNow.AddDays(1), + End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + Location = "Test Location", + OrganiserId = "organizer123", + Bookings = [], + GatheringCategoryDetails = [], + }; + + dbContext.Gatherings.Add(gathering); + await dbContext.SaveChangesAsync(); + + // Act + Gathering? result = await gatheringService.GetGathering(gathering.GatheringId); + + // Assert + Assert.NotNull(result); + Assert.Equal(gathering.GatheringId, result.GatheringId); + Assert.Equal(gathering.Name, result.Name); + Assert.Equal(gathering.Description, result.Description); + } + + [Fact] + public async Task GetGathering_WithNonExistentId_ShouldReturnNull() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + const long nonExistentId = 999; + + // Act + Gathering? result = await gatheringService.GetGathering(nonExistentId); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task GetGatherings_WithNameFilter_ShouldReturnFilteredResults() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + List gatherings = + [ + new() + { + Name = "XYZ Conference", + Description = "Description 1", + Start = DateTimeOffset.UtcNow.AddDays(1), + End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + Location = "Location 1", + OrganiserId = "organizer1", + Bookings = [], + GatheringCategoryDetails = [], + }, + new() + { + Name = "Art Workshop", + Description = "Description 2", + Start = DateTimeOffset.UtcNow.AddDays(2), + End = DateTimeOffset.UtcNow.AddDays(2).AddHours(2), + Location = "Location 2", + OrganiserId = "organizer2", + Bookings = [], + GatheringCategoryDetails = [], + }, + ]; + + dbContext.Gatherings.AddRange(gatherings); + await dbContext.SaveChangesAsync(); + + // Act + PageResult result = await gatheringService.GetGatherings( + attendeeId: null, + organiserId: null, + "XYZ", + startDateBefore: null, + startDateAfter: null, + endDateBefore: null, + endDateAfter: null, + isCancelled: null, + categoryIds: [], + offset: null, + limit: null + ); + + // Assert + Assert.NotNull(result); + Assert.Equal(expected: 1, result.TotalCount); + Assert.Equal("XYZ Conference", result.Items.First().Name); + } + + [Fact] + public async Task UpdateGathering_WithValidData_ShouldUpdateGathering() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + Gathering gathering = new() + { + Name = "Original Name", + Description = "Original Description", + Start = DateTimeOffset.UtcNow.AddDays(1), + End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + Location = "Original Location", + OrganiserId = "organizer123", + GatheringCategoryDetails = [], + }; + + dbContext.Gatherings.Add(gathering); + await dbContext.SaveChangesAsync(); + + GatheringReqDto updateDto = new( + gathering.GatheringId, + "Updated Name", + "Updated Description", + Start: DateTimeOffset.UtcNow.AddDays(2), + End: DateTimeOffset.UtcNow.AddDays(2).AddHours(3), + CancellationDateTime: null, + "Updated Location", + "organizer123", + "updated-cover.jpg", + GatheringCategoryDetails: [] + ); + + // Act + Gathering result = await gatheringService.UpdateGathering( + gathering.GatheringId, + updateDto + ); + + // Assert + Assert.NotNull(result); + Assert.Equal("Updated Name", result.Name); + Assert.Equal("Updated Description", result.Description); + Assert.Equal(updateDto.Start, result.Start); + Assert.Equal(updateDto.End, result.End); + Assert.Equal("Updated Location", result.Location); + Assert.Equal("updated-cover.jpg", result.CoverSrc); + } + + [Fact] + public async Task UpdateGathering_WithNonExistentId_ShouldThrowKeyNotFoundException() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + GatheringReqDto updateDto = new( + GatheringId: 999, + "Updated Name", + "Updated Description", + Start: DateTimeOffset.UtcNow.AddDays(2), + End: DateTimeOffset.UtcNow.AddDays(2).AddHours(3), + CancellationDateTime: null, + "Updated Location", + "organizer123", + CoverSrc: null, + GatheringCategoryDetails: [] + ); + + // Act & Assert + await Assert.ThrowsAsync(() => + gatheringService.UpdateGathering(gatheringId: 999, updateDto) + ); + } + + [Fact] + public async Task DeleteGathering_WithExistingId_ShouldDeleteGathering() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + Gathering gathering = new() + { + Name = "Test Gathering", + Description = "Test Description", + Start = DateTimeOffset.UtcNow.AddDays(1), + End = DateTimeOffset.UtcNow.AddDays(1).AddHours(2), + Location = "Test Location", + OrganiserId = "organizer123", + GatheringCategoryDetails = [], + }; + + dbContext.Gatherings.Add(gathering); + await dbContext.SaveChangesAsync(); + long gatheringId = gathering.GatheringId; + + // Act + await gatheringService.DeleteGathering(gatheringId); + + // Assert + Gathering? deletedGathering = await dbContext.Gatherings.FirstOrDefaultAsync(g => + g.GatheringId == gatheringId + ); + Assert.Null(deletedGathering); + } + + [Fact] + public async Task DeleteGathering_WithNonExistentId_ShouldThrowInvalidOperationException() + { + AppDbContext dbContext = await dbFixture.GetDbContext(); + GatheringService gatheringService = new(dbContext, validator: new GatheringValidator()); + + // Arrange + const long nonExistentId = 999; + + // Act & Assert + await Assert.ThrowsAsync(() => + gatheringService.DeleteGathering(nonExistentId) + ); + } +} From 06b2df297e68401fff4b6db1ac1b6b74c5cac02c Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 29 Nov 2025 19:31:45 +0800 Subject: [PATCH 10/10] chore: update --- tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs b/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs index caba54e..581f6c3 100644 --- a/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs +++ b/tests/Evently.Server.Test/Common/Setup/DatabaseFixture.cs @@ -24,6 +24,6 @@ public async Task GetDbContext() { public void Dispose() { _dbContext?.Dispose(); - _container.DisposeAsync(); + _container.DisposeAsync().AsTask().Wait(); } }