From c39fdceeb69c61f0bc4c6481b1880cd6aa1799d2 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 8 Apr 2025 23:26:23 +0800 Subject: [PATCH] :tada: Initial Commit --- .dockerignore | 25 +++ .gitignore | 6 + DysonNetwork.Sphere/Account/Account.cs | 60 ++++++ .../Account/AccountController.cs | 57 ++++++ DysonNetwork.Sphere/AppDatabase.cs | 92 +++++++++ DysonNetwork.Sphere/Dockerfile | 23 +++ .../DysonNetwork.Sphere.csproj | 32 +++ DysonNetwork.Sphere/DysonNetwork.Sphere.http | 6 + ...0250408152422_InitialMigration.Designer.cs | 190 ++++++++++++++++++ .../20250408152422_InitialMigration.cs | 105 ++++++++++ .../Migrations/AppDatabaseModelSnapshot.cs | 187 +++++++++++++++++ DysonNetwork.Sphere/Program.cs | 42 ++++ .../Properties/launchSettings.json | 23 +++ DysonNetwork.Sphere/appsettings.json | 12 ++ DysonNetwork.sln | 21 ++ DysonNetwork.sln.DotSettings.user | 2 + compose.yaml | 6 + 17 files changed, 889 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 DysonNetwork.Sphere/Account/Account.cs create mode 100644 DysonNetwork.Sphere/Account/AccountController.cs create mode 100644 DysonNetwork.Sphere/AppDatabase.cs create mode 100644 DysonNetwork.Sphere/Dockerfile create mode 100644 DysonNetwork.Sphere/DysonNetwork.Sphere.csproj create mode 100644 DysonNetwork.Sphere/DysonNetwork.Sphere.http create mode 100644 DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs create mode 100644 DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.cs create mode 100644 DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs create mode 100644 DysonNetwork.Sphere/Program.cs create mode 100644 DysonNetwork.Sphere/Properties/launchSettings.json create mode 100644 DysonNetwork.Sphere/appsettings.json create mode 100644 DysonNetwork.sln create mode 100644 DysonNetwork.sln.DotSettings.user create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4867c4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Sphere/Account/Account.cs new file mode 100644 index 0000000..4b8da1f --- /dev/null +++ b/DysonNetwork.Sphere/Account/Account.cs @@ -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 Contacts { get; set; } = new List(); + public ICollection AuthFactors { get; set; } = new List(); +} + +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 +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs new file mode 100644 index 0000000..267f96c --- /dev/null +++ b/DysonNetwork.Sphere/Account/AccountController.cs @@ -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(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> 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(StatusCodes.Status200OK)] + public async Task> CreateAccount([FromBody] AccountCreateRequest request) + { + var account = new Account + { + Name = request.Name, + Nick = request.Nick, + Contacts = new List() + { + new AccountContact + { + Type = AccountContactType.Email, + Content = request.Email + } + }, + AuthFactors = new List + { + new AccountAuthFactor + { + Type = AccountAuthFactorType.Password, + Secret = request.Password + }.HashSecret() + } + }; + + await db.Accounts.AddAsync(account); + await db.SaveChangesAsync(); + return account; + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs new file mode 100644 index 0000000..468c823 --- /dev/null +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -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 options) : DbContext(options) +{ + public DbSet Accounts { get; set; } + public DbSet AccountContacts { get; set; } + public DbSet 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(ModelBuilder modelBuilder) + where TEntity : BaseModel + { + modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null); + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + foreach (var entry in ChangeTracker.Entries()) + { + 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 +{ + public AppDatabase CreateDbContext(string[] args) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql( + configuration.GetConnectionString("App"), + o => o.UseNodaTime() + ).UseSnakeCaseNamingConvention(); + + return new AppDatabase(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Dockerfile b/DysonNetwork.Sphere/Dockerfile new file mode 100644 index 0000000..e6a5c35 --- /dev/null +++ b/DysonNetwork.Sphere/Dockerfile @@ -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"] diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj new file mode 100644 index 0000000..dbd07fd --- /dev/null +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -0,0 +1,32 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + .dockerignore + + + + diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.http b/DysonNetwork.Sphere/DysonNetwork.Sphere.http new file mode 100644 index 0000000..49c77d3 --- /dev/null +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.http @@ -0,0 +1,6 @@ +@DysonNetwork.Sphere_HostAddress = http://localhost:5071 + +GET {{DysonNetwork.Sphere_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs b/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs new file mode 100644 index 0000000..6a2abc7 --- /dev/null +++ b/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.Designer.cs @@ -0,0 +1,190 @@ +// +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 + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Nick") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("nick"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Secret") + .HasColumnType("text") + .HasColumnName("secret"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("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 + } + } +} diff --git a/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.cs b/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.cs new file mode 100644 index 0000000..29fcad3 --- /dev/null +++ b/DysonNetwork.Sphere/Migrations/20250408152422_InitialMigration.cs @@ -0,0 +1,105 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Sphere.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "accounts", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + nick = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(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(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + type = table.Column(type: "integer", nullable: false), + secret = table.Column(type: "text", nullable: true), + account_id = table.Column(type: "bigint", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(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(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + type = table.Column(type: "integer", nullable: false), + verified_at = table.Column(type: "timestamp with time zone", nullable: true), + content = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + account_id = table.Column(type: "bigint", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(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"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "account_auth_factors"); + + migrationBuilder.DropTable( + name: "account_contacts"); + + migrationBuilder.DropTable( + name: "accounts"); + } + } +} diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs new file mode 100644 index 0000000..8154da2 --- /dev/null +++ b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs @@ -0,0 +1,187 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("name"); + + b.Property("Nick") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("nick"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Secret") + .HasColumnType("text") + .HasColumnName("secret"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("bigint") + .HasColumnName("account_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("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 + } + } +} diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs new file mode 100644 index 0000000..db507e5 --- /dev/null +++ b/DysonNetwork.Sphere/Program.cs @@ -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(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(); + db.Database.Migrate(); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Sphere/Properties/launchSettings.json b/DysonNetwork.Sphere/Properties/launchSettings.json new file mode 100644 index 0000000..a6ab6aa --- /dev/null +++ b/DysonNetwork.Sphere/Properties/launchSettings.json @@ -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" + } + } + } +} diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json new file mode 100644 index 0000000..b886009 --- /dev/null +++ b/DysonNetwork.Sphere/appsettings.json @@ -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" + } +} diff --git a/DysonNetwork.sln b/DysonNetwork.sln new file mode 100644 index 0000000..ea7c8d0 --- /dev/null +++ b/DysonNetwork.sln @@ -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 diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user new file mode 100644 index 0000000..8c0e8ef --- /dev/null +++ b/DysonNetwork.sln.DotSettings.user @@ -0,0 +1,2 @@ + + ForceIncluded \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..caff736 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,6 @@ +services: + dysonnetwork.sphere: + image: dysonnetwork.sphere + build: + context: . + dockerfile: DysonNetwork.Sphere/Dockerfile