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/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/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/.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/Adapters/Data/AppDbContext.cs b/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs
deleted file mode 100644
index cbf9340..0000000
--- a/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs
+++ /dev/null
@@ -1,290 +0,0 @@
-using Evently.Server.Common.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;
-
-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) {
- base.OnModelCreating(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);
-
- modelBuilder.Entity().Property(c => c.CategoryId)
- .HasIdentityOptions(startValue: 20);
-
- 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";
-
- // 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" }
- );
-
- // 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 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);
-
- 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());
- }
- }
- }
-}
\ No newline at end of file
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/Data/AppDbContext.cs b/src/Evently.Server/Common/Data/AppDbContext.cs
new file mode 100644
index 0000000..e595566
--- /dev/null
+++ b/src/Evently.Server/Common/Data/AppDbContext.cs
@@ -0,0 +1,582 @@
+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;
+
+namespace Evently.Server.Common.Data;
+
+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)
+ {
+ base.OnModelCreating(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);
+
+ modelBuilder
+ .Entity()
+ .Property(c => c.CategoryId)
+ .HasIdentityOptions(startValue: 20);
+
+ 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";
+
+ // 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" }
+ );
+
+ // 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 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
+ );
+
+ 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());
+ }
+ }
+ }
+}
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..94cd494
--- /dev/null
+++ b/src/Evently.Server/Common/Data/Migrations/20250913035915_SQLServer.cs
@@ -0,0 +1,867 @@
+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");
+ }
+ }
+}
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..d005d21 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,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("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..d8bf36f
--- /dev/null
+++ b/src/Evently.Server/Common/Data/Migrations/20250927053802_UpdateSeededDates.cs
@@ -0,0 +1,603 @@
+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)
+ ),
+ }
+ );
+ }
+ }
+}
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/Domains/Entities/Account.cs b/src/Evently.Server/Common/Domains/Entities/Account.cs
deleted file mode 100644
index c635b1d..0000000
--- a/src/Evently.Server/Common/Domains/Entities/Account.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Microsoft.AspNetCore.Identity;
-using System.ComponentModel.DataAnnotations;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.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 List Bookings { get; set; } = [];
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Entities/Booking.cs b/src/Evently.Server/Common/Domains/Entities/Booking.cs
deleted file mode 100644
index 600ae0e..0000000
--- a/src/Evently.Server/Common/Domains/Entities/Booking.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Evently.Server.Common.Domains.Models;
-using Evently.Server.Common.Extensions;
-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;
-
-[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
-[SuppressMessage("ReSharper", "UnusedMember.Global")]
-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")]
- public string AttendeeId { get; set; } = string.Empty;
- [JsonIgnore] public Account? Account { get; set; }
- [NotMapped] public AccountDto? AccountDto => Account?.ToAccountDto();
-
- 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; }
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Entities/Category.cs b/src/Evently.Server/Common/Domains/Entities/Category.cs
deleted file mode 100644
index 5d26c69..0000000
--- a/src/Evently.Server/Common/Domains/Entities/Category.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.Domains.Entities;
-
-[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
-[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
-public class Category {
- [Key] public long CategoryId { get; set; }
-
- [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/Common/Domains/Entities/Gathering.cs b/src/Evently.Server/Common/Domains/Entities/Gathering.cs
deleted file mode 100644
index 9d990fa..0000000
--- a/src/Evently.Server/Common/Domains/Entities/Gathering.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.Domains.Entities;
-
-[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
-[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
-public class Gathering {
- [Key]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public long GatheringId { get; set; }
-
- [StringLength(100)] public string Name { 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(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;
- public DateTimeOffset? CancellationDateTime { get; set; }
-
- public List Bookings { get; set; } = [];
- public List GatheringCategoryDetails { get; set; } = [];
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs b/src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs
deleted file mode 100644
index a01019c..0000000
--- a/src/Evently.Server/Common/Domains/Entities/GatheringCategoryDetail.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.Domains.Entities;
-
-[PrimaryKey(propertyName: nameof(GatheringId), nameof(CategoryId))]
-[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
-[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; }
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs b/src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs
deleted file mode 100644
index 556d6c3..0000000
--- a/src/Evently.Server/Common/Domains/Exceptions/ExternalLoginProviderException.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Evently.Server.Common.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/Common/Domains/Interfaces/IAccountsService.cs
deleted file mode 100644
index 182095e..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IAccountsService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Evently.Server.Common.Domains.Entities;
-using System.Security.Claims;
-
-namespace Evently.Server.Common.Domains.Interfaces;
-
-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/Common/Domains/Interfaces/IBookingService.cs b/src/Evently.Server/Common/Domains/Interfaces/IBookingService.cs
deleted file mode 100644
index 70c0695..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IBookingService.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Models;
-
-namespace Evently.Server.Common.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,
- 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/Common/Domains/Interfaces/ICategoryService.cs b/src/Evently.Server/Common/Domains/Interfaces/ICategoryService.cs
deleted file mode 100644
index 6e6f7b5..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/ICategoryService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Models;
-
-namespace Evently.Server.Common.Domains.Interfaces;
-
-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/Common/Domains/Interfaces/IEmailerAdapter.cs b/src/Evently.Server/Common/Domains/Interfaces/IEmailerAdapter.cs
deleted file mode 100644
index e5affa4..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IEmailerAdapter.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Evently.Server.Common.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);
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs b/src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs
deleted file mode 100644
index e1ad4f8..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IGatheringService.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Models;
-
-namespace Evently.Server.Common.Domains.Interfaces;
-
-public interface IGatheringService {
- 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 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/Common/Domains/Interfaces/IMediaRenderer.cs b/src/Evently.Server/Common/Domains/Interfaces/IMediaRenderer.cs
deleted file mode 100644
index f2ee977..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IMediaRenderer.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.AspNetCore.Components;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.Domains.Interfaces;
-
-public interface IMediaRenderer {
- 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);
-}
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs b/src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs
deleted file mode 100644
index 2763b08..0000000
--- a/src/Evently.Server/Common/Domains/Interfaces/IObjectStorageService.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.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 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/Common/Domains/Models/BookingReqDto.cs b/src/Evently.Server/Common/Domains/Models/BookingReqDto.cs
deleted file mode 100644
index e0028c0..0000000
--- a/src/Evently.Server/Common/Domains/Models/BookingReqDto.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Evently.Server.Common.Domains.Models;
-
-public sealed record BookingReqDto(
- string BookingId,
- string AttendeeId,
- long GatheringId,
- DateTimeOffset CreationDateTime,
- DateTimeOffset? CheckInDateTime,
- DateTimeOffset? CheckoutDateTime,
- DateTimeOffset? CancellationDateTime);
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs b/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs
deleted file mode 100644
index 1f45531..0000000
--- a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Evently.Server.Common.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
-);
\ No newline at end of file
diff --git a/src/Evently.Server/Common/Domains/Models/PageResult.cs b/src/Evently.Server/Common/Domains/Models/PageResult.cs
deleted file mode 100644
index 217de02..0000000
--- a/src/Evently.Server/Common/Domains/Models/PageResult.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Evently.Server.Common.Domains.Models;
-
-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/Common/Domains/Models/Settings.cs b/src/Evently.Server/Common/Domains/Models/Settings.cs
deleted file mode 100644
index 073e9e7..0000000
--- a/src/Evently.Server/Common/Domains/Models/Settings.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.ComponentModel.DataAnnotations.Schema;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Evently.Server.Common.Domains.Models;
-
-public sealed class Settings {
- public StorageAccount StorageAccount { get; init; } = new();
-
- [NotMapped] public AuthSetting Authentication { 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 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();
-}
-
-[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
-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 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;
-}
\ 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..19cea1c 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);
+ [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);
- //
-}
\ No newline at end of file
+ [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 b8bf244..f348bef 100644
--- a/src/Evently.Server/Common/Extensions/MapperExtension.cs
+++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs
@@ -1,61 +1,72 @@
-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;
-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 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 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
- );
- }
-}
\ No newline at end of file
+ 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 51a1105..a9cee08 100644
--- a/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs
+++ b/src/Evently.Server/Common/Extensions/ServiceContainerExtensions.cs
@@ -1,22 +1,26 @@
-using Evently.Server.Common.Domains.Models;
+using Evently.Server.Domains.Models;
using Microsoft.Extensions.Options;
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 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();
- // 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;
- }
-}
\ No newline at end of file
+ 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 135b954..d9ce25e 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) {
- uriBuilder.Port = request.Host.Port.Value;
- }
+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;
- }
+ 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);
- }
-}
\ No newline at end of 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);
+ }
+}
diff --git a/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs b/src/Evently.Server/Common/Middlewares/GlobalExceptionHandler.cs
index 899e8dc..4695484 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 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;
- }
-}
\ No newline at end of file
+ return true;
+ }
+}
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.Server/Domains/Entities/Account.cs b/src/Evently.Server/Domains/Entities/Account.cs
new file mode 100644
index 0000000..e24c4ab
--- /dev/null
+++ b/src/Evently.Server/Domains/Entities/Account.cs
@@ -0,0 +1,15 @@
+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 List Bookings { get; set; } = [];
+}
diff --git a/src/Evently.Server/Domains/Entities/Booking.cs b/src/Evently.Server/Domains/Entities/Booking.cs
new file mode 100644
index 0000000..b04cccd
--- /dev/null
+++ b/src/Evently.Server/Domains/Entities/Booking.cs
@@ -0,0 +1,41 @@
+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
+{
+ [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;
+
+ [JsonIgnore]
+ public Account? Account { get; set; }
+
+ [NotMapped]
+ public AccountDto? AccountDto => Account?.ToAccountDto();
+
+ 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; }
+}
diff --git a/src/Evently.Server/Domains/Entities/Category.cs b/src/Evently.Server/Domains/Entities/Category.cs
new file mode 100644
index 0000000..38e6cf3
--- /dev/null
+++ b/src/Evently.Server/Domains/Entities/Category.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Evently.Server.Domains.Entities;
+
+[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
+[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
+public class Category
+{
+ [Key]
+ public long CategoryId { get; set; }
+
+ [StringLength(100)]
+ public string CategoryName { get; set; } = string.Empty;
+ public bool Approved { 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
new file mode 100644
index 0000000..e6c6936
--- /dev/null
+++ b/src/Evently.Server/Domains/Entities/Gathering.cs
@@ -0,0 +1,39 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Evently.Server.Domains.Entities;
+
+[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
+[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
+public class Gathering
+{
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public long GatheringId { get; set; }
+
+ [StringLength(100)]
+ public string Name { 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(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;
+
+ public DateTimeOffset? CancellationDateTime { 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
new file mode 100644
index 0000000..7781af2
--- /dev/null
+++ b/src/Evently.Server/Domains/Entities/GatheringCategoryDetail.cs
@@ -0,0 +1,15 @@
+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 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
new file mode 100644
index 0000000..c97efcb
--- /dev/null
+++ b/src/Evently.Server/Domains/Exceptions/ExternalLoginProviderException.cs
@@ -0,0 +1,4 @@
+namespace Evently.Server.Domains.Exceptions;
+
+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
new file mode 100644
index 0000000..2d3c4a4
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IAccountsService.cs
@@ -0,0 +1,10 @@
+using System.Security.Claims;
+using Evently.Server.Domains.Entities;
+
+namespace Evently.Server.Domains.Interfaces;
+
+public interface IAccountsService
+{
+ 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
new file mode 100644
index 0000000..1d42d6b
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IBookingService.cs
@@ -0,0 +1,27 @@
+using Evently.Server.Domains.Entities;
+using Evently.Server.Domains.Models;
+
+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,
+ 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/Domains/Interfaces/ICategoryService.cs b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs
new file mode 100644
index 0000000..c4eea00
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/ICategoryService.cs
@@ -0,0 +1,10 @@
+using Evently.Server.Domains.Entities;
+using Evently.Server.Domains.Models;
+
+namespace Evently.Server.Domains.Interfaces;
+
+public interface ICategoryService
+{
+ 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
new file mode 100644
index 0000000..3e3721d
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IEmailerAdapter.cs
@@ -0,0 +1,7 @@
+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);
+}
diff --git a/src/Evently.Server/Domains/Interfaces/IGatheringService.cs b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs
new file mode 100644
index 0000000..d7d0f80
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IGatheringService.cs
@@ -0,0 +1,27 @@
+using Evently.Server.Domains.Entities;
+using Evently.Server.Domains.Models;
+
+namespace Evently.Server.Domains.Interfaces;
+
+public interface IGatheringService
+{
+ 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 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
new file mode 100644
index 0000000..f0296c0
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IMediaRenderer.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Components;
+
+namespace Evently.Server.Domains.Interfaces;
+
+public interface IMediaRenderer
+{
+ 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);
+}
diff --git a/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs
new file mode 100644
index 0000000..6ebdd83
--- /dev/null
+++ b/src/Evently.Server/Domains/Interfaces/IObjectStorageService.cs
@@ -0,0 +1,21 @@
+using System.Diagnostics.CodeAnalysis;
+
+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 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/Common/Domains/Models/AccountDto.cs b/src/Evently.Server/Domains/Models/AccountDto.cs
similarity index 61%
rename from src/Evently.Server/Common/Domains/Models/AccountDto.cs
rename to src/Evently.Server/Domains/Models/AccountDto.cs
index 6f38ea7..e251d3f 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
+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
new file mode 100644
index 0000000..b1e7357
--- /dev/null
+++ b/src/Evently.Server/Domains/Models/BookingReqDto.cs
@@ -0,0 +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
+);
diff --git a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs b/src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs
similarity index 63%
rename from src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs
rename to src/Evently.Server/Domains/Models/GatheringCategoryDetailDto.cs
index 24127ea..ba0ec6d 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
+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
new file mode 100644
index 0000000..d3ce03f
--- /dev/null
+++ b/src/Evently.Server/Domains/Models/GatheringReqDto.cs
@@ -0,0 +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
+);
diff --git a/src/Evently.Server/Domains/Models/PageResult.cs b/src/Evently.Server/Domains/Models/PageResult.cs
new file mode 100644
index 0000000..9994883
--- /dev/null
+++ b/src/Evently.Server/Domains/Models/PageResult.cs
@@ -0,0 +1,7 @@
+namespace Evently.Server.Domains.Models;
+
+public sealed class PageResult
+{
+ 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
new file mode 100644
index 0000000..95221cd
--- /dev/null
+++ b/src/Evently.Server/Domains/Models/Settings.cs
@@ -0,0 +1,53 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Evently.Server.Domains.Models;
+
+public sealed class Settings
+{
+ public StorageAccount StorageAccount { get; init; } = new();
+
+ [NotMapped]
+ public AuthSetting Authentication { 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 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();
+}
+
+[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
+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 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;
+}
diff --git a/src/Evently.Server/Evently.Server.csproj b/src/Evently.Server/Evently.Server.csproj
index 839adac..55a151b 100644
--- a/src/Evently.Server/Evently.Server.csproj
+++ b/src/Evently.Server/Evently.Server.csproj
@@ -1,55 +1,61 @@
-
+
+
+ net10.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
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.0.0
+
+
+
+ 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" />
+
-
-
-
-
\ 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..984ebec 100644
--- a/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs
+++ b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs
@@ -1,13 +1,13 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Exceptions;
-using Evently.Server.Common.Domains.Interfaces;
+using System.Security.Claims;
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;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
-using System.Security.Claims;
namespace Evently.Server.Features.Accounts.Controllers;
@@ -15,89 +15,109 @@ namespace Evently.Server.Features.Accounts.Controllers;
[ApiController]
[Route("api/v1/Auth/external")]
public sealed class AccountController(
- IAccountsService accountService,
- ILogger logger) : ControllerBase {
- private readonly Dictionary _authSchemes = new() {
- { "google", GoogleDefaults.AuthenticationScheme },
- { "microsoft", MicrosoftAccountDefaults.AuthenticationScheme },
- };
+ IAccountsService accountService,
+ ILogger logger
+) : ControllerBase
+{
+ 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 });
- }
-}
\ No newline at end of file
+ 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 333de27..686bbbb 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.Common.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 {
- protected override async Task HandleRequirementAsync(
- AuthorizationHandlerContext context,
- SameAccountRequirement requirement,
- string? identityUserId) {
- ClaimsPrincipal principal = context.User;
- Account? user = await FindByClaimsPrincipalAsync(userManager, principal);
+ : AuthorizationHandler
+{
+ 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";
-}
\ No newline at end of file
+public class SameAccountRequirement : IAuthorizationRequirement
+{
+ 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 574a128..e3232f9 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) {
- IAuthorizationService authorizationService =
- controller.HttpContext.RequestServices.GetRequiredService();
+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) {
- 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;
- }
-}
\ No newline at end of file
+ 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 dead37a..db8d6cb 100644
--- a/src/Evently.Server/Features/Accounts/Services/AccountService.cs
+++ b/src/Evently.Server/Features/Accounts/Services/AccountService.cs
@@ -1,68 +1,88 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Exceptions;
-using Evently.Server.Common.Domains.Interfaces;
-using Microsoft.AspNetCore.Identity;
-using System.Security.Claims;
+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;
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 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);
- 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(", ",
- values: result.Errors.Select(x => x.Description))}");
- }
+ 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(", ",
- values: loginResult.Errors.Select(err => err.Description))}");
- }
+ 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();
-}
\ No newline at end of file
+ [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 f5aa168..cc1d84a 100644
--- a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs
+++ b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs
@@ -1,20 +1,28 @@
-using Evently.Server.Common.Domains.Entities;
+using Evently.Server.Domains.Entities;
using FluentValidation;
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 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.");
+ }
- if (string.IsNullOrEmpty(value.BookingId)) {
- context.AddFailure("BookingId is required.");
- }
- });
- }
-}
\ No newline at end of file
+ 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 a09db98..433717a 100644
--- a/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs
+++ b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs
@@ -1,103 +1,133 @@
-using Evently.Server.Common.Domains.Entities;
-using Evently.Server.Common.Domains.Interfaces;
-using Evently.Server.Common.Domains.Models;
+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;
[ApiController]
[Route("api/v1/[controller]")]
-public sealed class BookingsController(IBookingService bookingService, ChannelWriter emailQueue, ILogger