🎉 Initial Commit
This commit is contained in:
		
							
								
								
									
										25
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| **/.dockerignore | ||||
| **/.env | ||||
| **/.git | ||||
| **/.gitignore | ||||
| **/.project | ||||
| **/.settings | ||||
| **/.toolstarget | ||||
| **/.vs | ||||
| **/.vscode | ||||
| **/.idea | ||||
| **/*.*proj.user | ||||
| **/*.dbmdl | ||||
| **/*.jfm | ||||
| **/azds.yaml | ||||
| **/bin | ||||
| **/charts | ||||
| **/docker-compose* | ||||
| **/Dockerfile* | ||||
| **/node_modules | ||||
| **/npm-debug.log | ||||
| **/obj | ||||
| **/secrets.dev.yaml | ||||
| **/values.dev.yaml | ||||
| LICENSE | ||||
| README.md | ||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| bin/ | ||||
| obj/ | ||||
| /packages/ | ||||
| riderModule.iml | ||||
| /_ReSharper.Caches/ | ||||
| .idea | ||||
							
								
								
									
										60
									
								
								DysonNetwork.Sphere/Account/Account.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								DysonNetwork.Sphere/Account/Account.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.Text; | ||||
| using NodaTime; | ||||
| using Org.BouncyCastle.Crypto.Generators; | ||||
| using Org.BouncyCastle.Security; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Account; | ||||
|  | ||||
| public class Account : BaseModel | ||||
| { | ||||
|     public long Id { get; set; } | ||||
|     [MaxLength(256)] public string Name { get; set; } = string.Empty; | ||||
|     [MaxLength(256)] public string Nick { get; set; } = string.Empty; | ||||
|      | ||||
|     public ICollection<AccountContact> Contacts { get; set; } = new List<AccountContact>(); | ||||
|     public ICollection<AccountAuthFactor> AuthFactors { get; set; } = new List<AccountAuthFactor>(); | ||||
| } | ||||
|  | ||||
| public class AccountContact : BaseModel | ||||
| { | ||||
|     public long Id { get; set; } | ||||
|     public AccountContactType Type { get; set; } | ||||
|     public Instant? VerifiedAt { get; set; } | ||||
|     [MaxLength(1024)] public string Content { get; set; } = string.Empty; | ||||
|      | ||||
|     public Account Account { get; set; } = null!; | ||||
| } | ||||
|  | ||||
| public enum AccountContactType | ||||
| { | ||||
|     Email, PhoneNumber, Address | ||||
| } | ||||
|  | ||||
| public class AccountAuthFactor : BaseModel | ||||
| { | ||||
|     public long Id { get; set; } | ||||
|     public AccountAuthFactorType Type { get; set; } | ||||
|     public string? Secret { get; set; } = null; | ||||
|      | ||||
|     public Account Account { get; set; } = null!; | ||||
|  | ||||
|     public AccountAuthFactor HashSecret(int cost = 12) | ||||
|     { | ||||
|         if(Secret == null) return this; | ||||
|          | ||||
|         var passwordBytes = Encoding.UTF8.GetBytes(Secret); | ||||
|         var random = new SecureRandom(); | ||||
|         var salt = new byte[16]; | ||||
|         random.NextBytes(salt); | ||||
|         var hashed = BCrypt.Generate(passwordBytes, salt, cost); | ||||
|         Secret = Convert.ToBase64String(hashed); | ||||
|  | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
|  | ||||
| public enum AccountAuthFactorType | ||||
| { | ||||
|     Password, EmailCode, InAppCode, TimedCode | ||||
| } | ||||
							
								
								
									
										57
									
								
								DysonNetwork.Sphere/Account/AccountController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								DysonNetwork.Sphere/Account/AccountController.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Account; | ||||
|  | ||||
| [ApiController] | ||||
| [Route("/accounts")] | ||||
| public class AccountController(AppDatabase db) | ||||
| { | ||||
|     [HttpGet("{name}")] | ||||
|     [ProducesResponseType<Account>(StatusCodes.Status200OK)] | ||||
|     [ProducesResponseType(StatusCodes.Status404NotFound)] | ||||
|     public async Task<ActionResult<Account?>> GetByName(string name) | ||||
|     { | ||||
|         var account = await db.Accounts.FindAsync(name); | ||||
|         return account; | ||||
|     } | ||||
|  | ||||
|     public class AccountCreateRequest | ||||
|     { | ||||
|         [Required] [MaxLength(256)] public string Name { get; set; } = string.Empty; | ||||
|         [Required] [MaxLength(256)] public string Nick { get; set; } = string.Empty; | ||||
|         [Required] [MaxLength(1024)] public string Email { get; set; } = string.Empty; | ||||
|         [Required] [MinLength(4)] [MaxLength(128)] public string Password { get; set; } = string.Empty; | ||||
|     } | ||||
|      | ||||
|     [HttpPost] | ||||
|     [ProducesResponseType<Account>(StatusCodes.Status200OK)] | ||||
|     public async Task<ActionResult<Account>> CreateAccount([FromBody] AccountCreateRequest request) | ||||
|     { | ||||
|         var account = new Account | ||||
|         { | ||||
|             Name = request.Name, | ||||
|             Nick = request.Nick, | ||||
|             Contacts = new List<AccountContact>() | ||||
|             { | ||||
|                 new AccountContact | ||||
|                 { | ||||
|                     Type = AccountContactType.Email, | ||||
|                     Content = request.Email | ||||
|                 } | ||||
|             }, | ||||
|             AuthFactors = new List<AccountAuthFactor> | ||||
|             { | ||||
|                 new AccountAuthFactor | ||||
|                 { | ||||
|                     Type = AccountAuthFactorType.Password, | ||||
|                     Secret = request.Password | ||||
|                 }.HashSecret() | ||||
|             } | ||||
|         }; | ||||
|          | ||||
|         await db.Accounts.AddAsync(account); | ||||
|         await db.SaveChangesAsync(); | ||||
|         return account; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										92
									
								
								DysonNetwork.Sphere/AppDatabase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								DysonNetwork.Sphere/AppDatabase.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Design; | ||||
| using NodaTime; | ||||
|  | ||||
| namespace DysonNetwork.Sphere; | ||||
|  | ||||
| public abstract class BaseModel | ||||
| { | ||||
|     public Instant CreatedAt { get; set; } | ||||
|     public Instant UpdatedAt { get; set; } | ||||
|     public Instant? DeletedAt { get; set; } | ||||
| } | ||||
|  | ||||
| public class AppDatabase(DbContextOptions<AppDatabase> options) : DbContext(options) | ||||
| { | ||||
|     public DbSet<Account.Account> Accounts { get; set; } | ||||
|     public DbSet<Account.AccountContact> AccountContacts { get; set; } | ||||
|     public DbSet<Account.AccountAuthFactor> AccountAuthFactors { get; set; } | ||||
|  | ||||
|     protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||
|     { | ||||
|         base.OnModelCreating(modelBuilder); | ||||
|  | ||||
|         // Automatically apply soft-delete filter to all entities inheriting BaseModel | ||||
|         foreach (var entityType in modelBuilder.Model.GetEntityTypes()) | ||||
|         { | ||||
|             if (typeof(BaseModel).IsAssignableFrom(entityType.ClrType)) | ||||
|             { | ||||
|                 var method = typeof(AppDatabase) | ||||
|                     .GetMethod(nameof(SetSoftDeleteFilter), | ||||
|                         System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)! | ||||
|                     .MakeGenericMethod(entityType.ClrType); | ||||
|  | ||||
|                 method.Invoke(null, [modelBuilder]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void SetSoftDeleteFilter<TEntity>(ModelBuilder modelBuilder) | ||||
|         where TEntity : BaseModel | ||||
|     { | ||||
|         modelBuilder.Entity<TEntity>().HasQueryFilter(e => e.DeletedAt == null); | ||||
|     } | ||||
|  | ||||
|     public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|  | ||||
|         foreach (var entry in ChangeTracker.Entries<BaseModel>()) | ||||
|         { | ||||
|             switch (entry.State) | ||||
|             { | ||||
|                 case EntityState.Added: | ||||
|                     entry.Entity.CreatedAt = now; | ||||
|                     entry.Entity.UpdatedAt = now; | ||||
|                     break; | ||||
|                 case EntityState.Modified: | ||||
|                     entry.Entity.UpdatedAt = now; | ||||
|                     break; | ||||
|                 case EntityState.Deleted: | ||||
|                     entry.State = EntityState.Modified; | ||||
|                     entry.Entity.DeletedAt = now; | ||||
|                     break; | ||||
|                 case EntityState.Detached: | ||||
|                 case EntityState.Unchanged: | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return await base.SaveChangesAsync(cancellationToken); | ||||
|     } | ||||
| } | ||||
|  | ||||
| public class AppDatabaseFactory : IDesignTimeDbContextFactory<AppDatabase> | ||||
| { | ||||
|     public AppDatabase CreateDbContext(string[] args) | ||||
|     { | ||||
|         var configuration = new ConfigurationBuilder() | ||||
|             .SetBasePath(Directory.GetCurrentDirectory()) | ||||
|             .AddJsonFile("appsettings.json") | ||||
|             .Build(); | ||||
|  | ||||
|         var optionsBuilder = new DbContextOptionsBuilder<AppDatabase>(); | ||||
|         optionsBuilder.UseNpgsql( | ||||
|             configuration.GetConnectionString("App"), | ||||
|             o => o.UseNodaTime() | ||||
|         ).UseSnakeCaseNamingConvention(); | ||||
|  | ||||
|         return new AppDatabase(optionsBuilder.Options); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								DysonNetwork.Sphere/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								DysonNetwork.Sphere/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base | ||||
| USER $APP_UID | ||||
| WORKDIR /app | ||||
| EXPOSE 8080 | ||||
| EXPOSE 8081 | ||||
|  | ||||
| FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build | ||||
| ARG BUILD_CONFIGURATION=Release | ||||
| WORKDIR /src | ||||
| COPY ["DysonNetwork.Sphere/DysonNetwork.Sphere.csproj", "DysonNetwork.Sphere/"] | ||||
| RUN dotnet restore "DysonNetwork.Sphere/DysonNetwork.Sphere.csproj" | ||||
| COPY . . | ||||
| WORKDIR "/src/DysonNetwork.Sphere" | ||||
| RUN dotnet build "DysonNetwork.Sphere.csproj" -c $BUILD_CONFIGURATION -o /app/build | ||||
|  | ||||
| FROM build AS publish | ||||
| ARG BUILD_CONFIGURATION=Release | ||||
| RUN dotnet publish "DysonNetwork.Sphere.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false | ||||
|  | ||||
| FROM base AS final | ||||
| WORKDIR /app | ||||
| COPY --from=publish /app/publish . | ||||
| ENTRYPOINT ["dotnet", "DysonNetwork.Sphere.dll"] | ||||
							
								
								
									
										32
									
								
								DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||||
|  | ||||
|     <PropertyGroup> | ||||
|         <TargetFramework>net9.0</TargetFramework> | ||||
|         <Nullable>enable</Nullable> | ||||
|         <ImplicitUsings>enable</ImplicitUsings> | ||||
|         <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | ||||
|     </PropertyGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" /> | ||||
|         <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" /> | ||||
|         <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2"/> | ||||
|         <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3"> | ||||
|           <PrivateAssets>all</PrivateAssets> | ||||
|           <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|         </PackageReference> | ||||
|         <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" /> | ||||
|         <PackageReference Include="NodaTime" Version="3.2.2" /> | ||||
|         <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" /> | ||||
|         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" /> | ||||
|     </ItemGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|       <Content Include="..\.dockerignore"> | ||||
|         <Link>.dockerignore</Link> | ||||
|       </Content> | ||||
|     </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										6
									
								
								DysonNetwork.Sphere/DysonNetwork.Sphere.http
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								DysonNetwork.Sphere/DysonNetwork.Sphere.http
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| @DysonNetwork.Sphere_HostAddress = http://localhost:5071 | ||||
|  | ||||
| GET {{DysonNetwork.Sphere_HostAddress}}/weatherforecast/ | ||||
| Accept: application/json | ||||
|  | ||||
| ### | ||||
							
								
								
									
										190
									
								
								DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| // <auto-generated /> | ||||
| using DysonNetwork.Sphere; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     [Migration("20250408152422_InitialMigration")] | ||||
|     partial class InitialMigration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.3") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<string>("Nick") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("nick"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_accounts"); | ||||
|  | ||||
|                     b.ToTable("accounts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<long>("AccountId") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Secret") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("secret"); | ||||
|  | ||||
|                     b.Property<int>("Type") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("type"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_account_auth_factors"); | ||||
|  | ||||
|                     b.HasIndex("AccountId") | ||||
|                         .HasDatabaseName("ix_account_auth_factors_account_id"); | ||||
|  | ||||
|                     b.ToTable("account_auth_factors", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<long>("AccountId") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<string>("Content") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("content"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<int>("Type") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("type"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("VerifiedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("verified_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_account_contacts"); | ||||
|  | ||||
|                     b.HasIndex("AccountId") | ||||
|                         .HasDatabaseName("ix_account_contacts_account_id"); | ||||
|  | ||||
|                     b.ToTable("account_contacts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") | ||||
|                         .WithMany("AuthFactors") | ||||
|                         .HasForeignKey("AccountId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_account_auth_factors_accounts_account_id"); | ||||
|  | ||||
|                     b.Navigation("Account"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") | ||||
|                         .WithMany("Contacts") | ||||
|                         .HasForeignKey("AccountId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_account_contacts_accounts_account_id"); | ||||
|  | ||||
|                     b.Navigation("Account"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => | ||||
|                 { | ||||
|                     b.Navigation("AuthFactors"); | ||||
|  | ||||
|                     b.Navigation("Contacts"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,105 @@ | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class InitialMigration : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "accounts", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<long>(type: "bigint", nullable: false) | ||||
|                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), | ||||
|                     name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), | ||||
|                     nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), | ||||
|                     created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_accounts", x => x.id); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_auth_factors", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<long>(type: "bigint", nullable: false) | ||||
|                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     secret = table.Column<string>(type: "text", nullable: true), | ||||
|                     account_id = table.Column<long>(type: "bigint", nullable: false), | ||||
|                     created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_account_auth_factors", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_auth_factors_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "account_contacts", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<long>(type: "bigint", nullable: false) | ||||
|                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), | ||||
|                     type = table.Column<int>(type: "integer", nullable: false), | ||||
|                     verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true), | ||||
|                     content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false), | ||||
|                     account_id = table.Column<long>(type: "bigint", nullable: false), | ||||
|                     created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false), | ||||
|                     deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_account_contacts", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_account_contacts_accounts_account_id", | ||||
|                         column: x => x.account_id, | ||||
|                         principalTable: "accounts", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_auth_factors_account_id", | ||||
|                 table: "account_auth_factors", | ||||
|                 column: "account_id"); | ||||
|  | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_account_contacts_account_id", | ||||
|                 table: "account_contacts", | ||||
|                 column: "account_id"); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_auth_factors"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "account_contacts"); | ||||
|  | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "accounts"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										187
									
								
								DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| // <auto-generated /> | ||||
| using DysonNetwork.Sphere; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
|  | ||||
| #nullable disable | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Migrations | ||||
| { | ||||
|     [DbContext(typeof(AppDatabase))] | ||||
|     partial class AppDatabaseModelSnapshot : ModelSnapshot | ||||
|     { | ||||
|         protected override void BuildModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.3") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
|  | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("name"); | ||||
|  | ||||
|                     b.Property<string>("Nick") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("nick"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_accounts"); | ||||
|  | ||||
|                     b.ToTable("accounts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<long>("AccountId") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<string>("Secret") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("secret"); | ||||
|  | ||||
|                     b.Property<int>("Type") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("type"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_account_auth_factors"); | ||||
|  | ||||
|                     b.HasIndex("AccountId") | ||||
|                         .HasDatabaseName("ix_account_auth_factors_account_id"); | ||||
|  | ||||
|                     b.ToTable("account_auth_factors", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => | ||||
|                 { | ||||
|                     b.Property<long>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("id"); | ||||
|  | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||
|  | ||||
|                     b.Property<long>("AccountId") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("account_id"); | ||||
|  | ||||
|                     b.Property<string>("Content") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(1024) | ||||
|                         .HasColumnType("character varying(1024)") | ||||
|                         .HasColumnName("content"); | ||||
|  | ||||
|                     b.Property<Instant>("CreatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("created_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("DeletedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("deleted_at"); | ||||
|  | ||||
|                     b.Property<int>("Type") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("type"); | ||||
|  | ||||
|                     b.Property<Instant>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
|  | ||||
|                     b.Property<Instant?>("VerifiedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("verified_at"); | ||||
|  | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_account_contacts"); | ||||
|  | ||||
|                     b.HasIndex("AccountId") | ||||
|                         .HasDatabaseName("ix_account_contacts_account_id"); | ||||
|  | ||||
|                     b.ToTable("account_contacts", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") | ||||
|                         .WithMany("AuthFactors") | ||||
|                         .HasForeignKey("AccountId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_account_auth_factors_accounts_account_id"); | ||||
|  | ||||
|                     b.Navigation("Account"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => | ||||
|                 { | ||||
|                     b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") | ||||
|                         .WithMany("Contacts") | ||||
|                         .HasForeignKey("AccountId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_account_contacts_accounts_account_id"); | ||||
|  | ||||
|                     b.Navigation("Account"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => | ||||
|                 { | ||||
|                     b.Navigation("AuthFactors"); | ||||
|  | ||||
|                     b.Navigation("Contacts"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								DysonNetwork.Sphere/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								DysonNetwork.Sphere/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| using System.Text.Json; | ||||
| using DysonNetwork.Sphere; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using NodaTime.Serialization.SystemTextJson; | ||||
|  | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
|  | ||||
| // Add services to the container. | ||||
|  | ||||
| builder.Services.AddDbContext<AppDatabase>(opt => | ||||
|     opt.UseNpgsql( | ||||
|         builder.Configuration.GetConnectionString("App"), | ||||
|         o => o.UseNodaTime() | ||||
|     ).UseSnakeCaseNamingConvention() | ||||
| ); | ||||
|  | ||||
| builder.Services.AddControllers().AddJsonOptions(options => | ||||
| { | ||||
|     options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; | ||||
|     options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; | ||||
|  | ||||
|     options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); | ||||
| }); | ||||
| builder.Services.AddOpenApi(); | ||||
|  | ||||
| var app = builder.Build(); | ||||
|  | ||||
| if (app.Environment.IsDevelopment()) app.MapOpenApi(); | ||||
|  | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| { | ||||
|     var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); | ||||
|     db.Database.Migrate(); | ||||
| } | ||||
|  | ||||
| app.UseHttpsRedirection(); | ||||
| app.UseAuthorization(); | ||||
|  | ||||
| app.MapControllers(); | ||||
|  | ||||
| app.Run(); | ||||
							
								
								
									
										23
									
								
								DysonNetwork.Sphere/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								DysonNetwork.Sphere/Properties/launchSettings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "$schema": "https://json.schemastore.org/launchsettings.json", | ||||
|   "profiles": { | ||||
|     "http": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "http://localhost:5071", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     }, | ||||
|     "https": { | ||||
|       "commandName": "Project", | ||||
|       "dotnetRunMessages": true, | ||||
|       "launchBrowser": false, | ||||
|       "applicationUrl": "https://localhost:7099;http://localhost:5071", | ||||
|       "environmentVariables": { | ||||
|         "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								DysonNetwork.Sphere/appsettings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								DysonNetwork.Sphere/appsettings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| { | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   }, | ||||
|   "AllowedHosts": "*", | ||||
|   "ConnectionStrings": { | ||||
|     "App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										21
									
								
								DysonNetwork.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								DysonNetwork.sln
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Sphere", "DysonNetwork.Sphere\DysonNetwork.Sphere.csproj", "{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A444D180-5B51-49C3-A35D-AA55832BBC66}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		compose.yaml = compose.yaml | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{CFF62EFA-F4C2-4FC7-8D97-25570B4DB452}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
							
								
								
									
										2
									
								
								DysonNetwork.sln.DotSettings.user
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								DysonNetwork.sln.DotSettings.user
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> | ||||
							
								
								
									
										6
									
								
								compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| services: | ||||
|   dysonnetwork.sphere: | ||||
|     image: dysonnetwork.sphere | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: DysonNetwork.Sphere/Dockerfile | ||||
		Reference in New Issue
	
	Block a user