Compare commits
2 Commits
afdbde951c
...
cde55eb237
Author | SHA1 | Date | |
---|---|---|---|
cde55eb237 | |||
03e26ef93c |
@@ -11,7 +11,11 @@
|
||||
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4" />
|
||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MimeTypes" Version="2.5.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -20,30 +24,30 @@
|
||||
<PackageReference Include="NetVips" Version="3.1.0" />
|
||||
<PackageReference Include="NetVips.Native.linux-x64" Version="8.17.1" />
|
||||
<PackageReference Include="NetVips.Native.osx-arm64" Version="8.17.1" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.2"/>
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0"/>
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.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.NetTopologySuite" Version="9.0.4"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0"/>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0"/>
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1"/>
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1"/>
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.1"/>
|
||||
<PackageReference Include="prometheus-net.EntityFramework" Version="0.9.5"/>
|
||||
<PackageReference Include="prometheus-net.SystemMetrics" Version="3.1.0"/>
|
||||
<PackageReference Include="Quartz" Version="3.14.0"/>
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
|
||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1"/>
|
||||
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1"/>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0"/>
|
||||
<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.NetTopologySuite" Version="9.0.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
||||
<PackageReference Include="prometheus-net.EntityFramework" Version="0.9.5" />
|
||||
<PackageReference Include="prometheus-net.SystemMetrics" Version="3.1.0" />
|
||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
|
||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
||||
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.0" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.0" />
|
||||
|
189
DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs
generated
Normal file
189
DysonNetwork.Drive/Migrations/20250713121317_InitialMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,189 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Drive;
|
||||
using DysonNetwork.Drive.Storage;
|
||||
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.Drive.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20250713121317_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.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>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Dictionary<string, object>>("FileMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("file_meta");
|
||||
|
||||
b.Property<bool>("HasCompression")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_compression");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<bool>("IsMarkedRecycle")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_marked_recycle");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("mime_type");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size");
|
||||
|
||||
b.Property<string>("StorageId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("storage_id");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("storage_url");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("UploadedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("uploaded_at");
|
||||
|
||||
b.Property<string>("UploadedTo")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("uploaded_to");
|
||||
|
||||
b.Property<Dictionary<string, object>>("UserMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("user_meta");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_files");
|
||||
|
||||
b.ToTable("files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("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<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Drive.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "files",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
file_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
user_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
sensitive_marks = table.Column<List<ContentSensitiveMark>>(type: "jsonb", nullable: true),
|
||||
mime_type = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
size = table.Column<long>(type: "bigint", nullable: false),
|
||||
uploaded_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
uploaded_to = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
has_compression = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_marked_recycle = table.Column<bool>(type: "boolean", nullable: false),
|
||||
storage_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
storage_url = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_files", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "file_references",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
file_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
usage = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
resource_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
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_file_references", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_file_references_files_file_id",
|
||||
column: x => x.file_id,
|
||||
principalTable: "files",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_file_references_file_id",
|
||||
table: "file_references",
|
||||
column: "file_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "file_references");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "files");
|
||||
}
|
||||
}
|
||||
}
|
186
DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
Normal file
186
DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Drive;
|
||||
using DysonNetwork.Drive.Storage;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
partial class AppDatabaseModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.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>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Dictionary<string, object>>("FileMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("file_meta");
|
||||
|
||||
b.Property<bool>("HasCompression")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_compression");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<bool>("IsMarkedRecycle")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_marked_recycle");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("mime_type");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size");
|
||||
|
||||
b.Property<string>("StorageId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("storage_id");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("storage_url");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("UploadedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("uploaded_at");
|
||||
|
||||
b.Property<string>("UploadedTo")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("uploaded_to");
|
||||
|
||||
b.Property<Dictionary<string, object>>("UserMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("user_meta");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_files");
|
||||
|
||||
b.ToTable("files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("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<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,8 @@
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379"
|
||||
"FastRetrieve": "localhost:6379",
|
||||
"Etcd": "etcd.orb.local:2379"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
@@ -125,5 +126,11 @@
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
],
|
||||
"Service": {
|
||||
"Name": "DysonNetwork.Drive",
|
||||
"Url": "http://localhost:5216",
|
||||
"ClientCert": "../Certificates/client.crt",
|
||||
"ClientKey": "../Certificates/client.key"
|
||||
}
|
||||
}
|
||||
|
@@ -5,11 +5,13 @@ using DysonNetwork.Pass.Email;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using OtpNet;
|
||||
using AuthSession = DysonNetwork.Pass.Auth.AuthSession;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
@@ -17,8 +19,8 @@ public class AccountService(
|
||||
AppDatabase db,
|
||||
MagicSpellService spells,
|
||||
AccountUsernameService uname,
|
||||
NotificationService nty,
|
||||
EmailService mailer,
|
||||
PusherService.PusherServiceClient pusher,
|
||||
IStringLocalizer<NotificationResource> localizer,
|
||||
ICacheService cache,
|
||||
ILogger<AccountService> logger
|
||||
@@ -353,13 +355,18 @@ public class AccountService(
|
||||
if (await _GetFactorCode(factor) is not null)
|
||||
throw new InvalidOperationException("A factor code has been sent and in active duration.");
|
||||
|
||||
await nty.SendNotification(
|
||||
account,
|
||||
"auth.verification",
|
||||
localizer["AuthCodeTitle"],
|
||||
null,
|
||||
localizer["AuthCodeBody", code],
|
||||
save: true
|
||||
await pusher.SendPushNotificationToUserAsync(
|
||||
new SendPushNotificationToUserRequest
|
||||
{
|
||||
UserId = account.Id.ToString(),
|
||||
Notification = new PushNotification
|
||||
{
|
||||
Topic = "auth.verification",
|
||||
Title = localizer["AuthCodeTitle"],
|
||||
Body = localizer["AuthCodeBody", code],
|
||||
IsSavable = false
|
||||
}
|
||||
}
|
||||
);
|
||||
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5));
|
||||
break;
|
||||
@@ -395,16 +402,17 @@ public class AccountService(
|
||||
return;
|
||||
}
|
||||
|
||||
await mailer.SendTemplatedEmailAsync<DysonNetwork.Pass.Pages.Emails.VerificationEmail, VerificationEmailModel>(
|
||||
account.Nick,
|
||||
contact.Content,
|
||||
localizer["VerificationEmail"],
|
||||
new VerificationEmailModel
|
||||
{
|
||||
Name = account.Name,
|
||||
Code = code
|
||||
}
|
||||
);
|
||||
await mailer
|
||||
.SendTemplatedEmailAsync<Pages.Emails.VerificationEmail, VerificationEmailModel>(
|
||||
account.Nick,
|
||||
contact.Content,
|
||||
localizer["VerificationEmail"],
|
||||
new VerificationEmailModel
|
||||
{
|
||||
Name = account.Name,
|
||||
Code = code
|
||||
}
|
||||
);
|
||||
|
||||
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30));
|
||||
break;
|
||||
@@ -489,7 +497,10 @@ public class AccountService(
|
||||
.ToListAsync();
|
||||
|
||||
if (session.Challenge.DeviceId is not null)
|
||||
await nty.UnsubscribePushNotifications(session.Challenge.DeviceId);
|
||||
await pusher.UnsubscribePushNotificationsAsync(new UnsubscribePushNotificationsRequest()
|
||||
{
|
||||
DeviceId = session.Challenge.DeviceId
|
||||
});
|
||||
|
||||
// The current session should be included in the sessions' list
|
||||
await db.AuthSessions
|
||||
@@ -648,7 +659,8 @@ public class AccountService(
|
||||
|
||||
if (missingId.Count != 0)
|
||||
{
|
||||
var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id }).ToList();
|
||||
var newProfiles = missingId.Select(id => new AccountProfile { Id = Guid.NewGuid(), AccountId = id })
|
||||
.ToList();
|
||||
await db.BulkInsertAsync(newProfiles);
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,24 @@ public class AccountServiceGrpc(
|
||||
return account.ToProtoValue();
|
||||
}
|
||||
|
||||
public override async Task<GetAccountBatchResponse> GetAccountBatch(GetAccountBatchRequest request, ServerCallContext context)
|
||||
{
|
||||
var accountIds = request.Id
|
||||
.Select(id => Guid.TryParse(id, out var accountId) ? accountId : (Guid?)null)
|
||||
.Where(id => id.HasValue)
|
||||
.Select(id => id!.Value)
|
||||
.ToList();
|
||||
|
||||
var accounts = await _db.Accounts
|
||||
.AsNoTracking()
|
||||
.Where(a => accountIds.Contains(a.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var response = new GetAccountBatchResponse();
|
||||
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<Shared.Proto.Account> CreateAccount(CreateAccountRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
|
@@ -1,292 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Pass;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class NotificationService(
|
||||
AppDatabase db,
|
||||
IHttpClientFactory httpFactory,
|
||||
IConfiguration config
|
||||
)
|
||||
{
|
||||
private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
||||
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
||||
|
||||
public async Task UnsubscribePushNotifications(string deviceId)
|
||||
{
|
||||
await db.NotificationPushSubscriptions
|
||||
.Where(s => s.DeviceId == deviceId)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
|
||||
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
||||
Account account,
|
||||
NotificationPushProvider provider,
|
||||
string deviceId,
|
||||
string deviceToken
|
||||
)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
// First check if a matching subscription exists
|
||||
var existingSubscription = await db.NotificationPushSubscriptions
|
||||
.Where(s => s.AccountId == account.Id)
|
||||
.Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (existingSubscription is not null)
|
||||
{
|
||||
// Update the existing subscription directly in the database
|
||||
await db.NotificationPushSubscriptions
|
||||
.Where(s => s.Id == existingSubscription.Id)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(s => s.DeviceId, deviceId)
|
||||
.SetProperty(s => s.DeviceToken, deviceToken)
|
||||
.SetProperty(s => s.UpdatedAt, now));
|
||||
|
||||
// Return the updated subscription
|
||||
existingSubscription.DeviceId = deviceId;
|
||||
existingSubscription.DeviceToken = deviceToken;
|
||||
existingSubscription.UpdatedAt = now;
|
||||
return existingSubscription;
|
||||
}
|
||||
|
||||
var subscription = new NotificationPushSubscription
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
DeviceToken = deviceToken,
|
||||
Provider = provider,
|
||||
AccountId = account.Id,
|
||||
};
|
||||
|
||||
db.NotificationPushSubscriptions.Add(subscription);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public async Task<Notification> SendNotification(
|
||||
Account account,
|
||||
string topic,
|
||||
string? title = null,
|
||||
string? subtitle = null,
|
||||
string? content = null,
|
||||
Dictionary<string, object>? meta = null,
|
||||
string? actionUri = null,
|
||||
bool isSilent = false,
|
||||
bool save = true
|
||||
)
|
||||
{
|
||||
if (title is null && subtitle is null && content is null)
|
||||
throw new ArgumentException("Unable to send notification that completely empty.");
|
||||
|
||||
meta ??= new Dictionary<string, object>();
|
||||
if (actionUri is not null) meta["action_uri"] = actionUri;
|
||||
|
||||
var notification = new Notification
|
||||
{
|
||||
Topic = topic,
|
||||
Title = title,
|
||||
Subtitle = subtitle,
|
||||
Content = content,
|
||||
Meta = meta,
|
||||
AccountId = account.Id,
|
||||
};
|
||||
|
||||
if (save)
|
||||
{
|
||||
db.Add(notification);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (!isSilent) _ = DeliveryNotification(notification);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
public async Task DeliveryNotification(Notification notification)
|
||||
{
|
||||
// Pushing the notification
|
||||
var subscribers = await db.NotificationPushSubscriptions
|
||||
.Where(s => s.AccountId == notification.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
public async Task MarkNotificationsViewed(ICollection<Notification> notifications)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList();
|
||||
if (id.Count == 0) return;
|
||||
|
||||
await db.Notifications
|
||||
.Where(n => id.Contains(n.Id))
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task BroadcastNotification(Notification notification, bool save = false)
|
||||
{
|
||||
var accounts = await db.Accounts.ToListAsync();
|
||||
|
||||
if (save)
|
||||
{
|
||||
var notifications = accounts.Select(x =>
|
||||
{
|
||||
var newNotification = new Notification
|
||||
{
|
||||
Topic = notification.Topic,
|
||||
Title = notification.Title,
|
||||
Subtitle = notification.Subtitle,
|
||||
Content = notification.Content,
|
||||
Meta = notification.Meta,
|
||||
Priority = notification.Priority,
|
||||
Account = x,
|
||||
AccountId = x.Id
|
||||
};
|
||||
return newNotification;
|
||||
}).ToList();
|
||||
await db.BulkInsertAsync(notifications);
|
||||
}
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
notification.Account = account;
|
||||
notification.AccountId = account.Id;
|
||||
}
|
||||
|
||||
var subscribers = await db.NotificationPushSubscriptions
|
||||
.ToListAsync();
|
||||
await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
public async Task SendNotificationBatch(Notification notification, List<Account> accounts, bool save = false)
|
||||
{
|
||||
if (save)
|
||||
{
|
||||
var notifications = accounts.Select(x =>
|
||||
{
|
||||
var newNotification = new Notification
|
||||
{
|
||||
Topic = notification.Topic,
|
||||
Title = notification.Title,
|
||||
Subtitle = notification.Subtitle,
|
||||
Content = notification.Content,
|
||||
Meta = notification.Meta,
|
||||
Priority = notification.Priority,
|
||||
Account = x,
|
||||
AccountId = x.Id
|
||||
};
|
||||
return newNotification;
|
||||
}).ToList();
|
||||
await db.BulkInsertAsync(notifications);
|
||||
}
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
notification.Account = account;
|
||||
notification.AccountId = account.Id;
|
||||
}
|
||||
|
||||
var accountsId = accounts.Select(x => x.Id).ToList();
|
||||
var subscribers = await db.NotificationPushSubscriptions
|
||||
.Where(s => accountsId.Contains(s.AccountId))
|
||||
.ToListAsync();
|
||||
await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
|
||||
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
{
|
||||
var subDict = subscriptions
|
||||
.GroupBy(x => x.Provider)
|
||||
.ToDictionary(x => x.Key, x => x.ToList());
|
||||
|
||||
var notifications = subDict.Select(value =>
|
||||
{
|
||||
var platformCode = value.Key switch
|
||||
{
|
||||
NotificationPushProvider.Apple => 1,
|
||||
NotificationPushProvider.Google => 2,
|
||||
_ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
|
||||
};
|
||||
|
||||
var tokens = value.Value.Select(x => x.DeviceToken).ToList();
|
||||
return _BuildNotificationPayload(notification, platformCode, tokens);
|
||||
}).ToList();
|
||||
|
||||
return notifications.ToList();
|
||||
}
|
||||
|
||||
private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
|
||||
IEnumerable<string> deviceTokens)
|
||||
{
|
||||
var alertDict = new Dictionary<string, object>();
|
||||
var dict = new Dictionary<string, object>
|
||||
{
|
||||
["notif_id"] = notification.Id.ToString(),
|
||||
["apns_id"] = notification.Id.ToString(),
|
||||
["topic"] = _notifyTopic,
|
||||
["tokens"] = deviceTokens,
|
||||
["data"] = new Dictionary<string, object>
|
||||
{
|
||||
["type"] = notification.Topic,
|
||||
["meta"] = notification.Meta ?? new Dictionary<string, object>(),
|
||||
},
|
||||
["mutable_content"] = true,
|
||||
["priority"] = notification.Priority >= 5 ? "high" : "normal",
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(notification.Title))
|
||||
{
|
||||
dict["title"] = notification.Title;
|
||||
alertDict["title"] = notification.Title;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(notification.Content))
|
||||
{
|
||||
dict["message"] = notification.Content;
|
||||
alertDict["body"] = notification.Content;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(notification.Subtitle))
|
||||
{
|
||||
dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
|
||||
alertDict["subtitle"] = notification.Subtitle;
|
||||
}
|
||||
|
||||
if (notification.Priority >= 5)
|
||||
dict["name"] = "default";
|
||||
|
||||
dict["platform"] = platformCode;
|
||||
dict["alert"] = alertDict;
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private async Task _PushNotification(Notification notification,
|
||||
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
{
|
||||
var subList = subscriptions.ToList();
|
||||
if (subList.Count == 0) return;
|
||||
|
||||
var requestDict = new Dictionary<string, object>
|
||||
{
|
||||
["notifications"] = _BuildNotificationPayload(notification, subList)
|
||||
};
|
||||
|
||||
var client = httpFactory.CreateClient();
|
||||
client.BaseAddress = _notifyEndpoint;
|
||||
var request = await client.PostAsync("/push", new StringContent(
|
||||
JsonSerializer.Serialize(requestDict),
|
||||
Encoding.UTF8,
|
||||
"application/json"
|
||||
));
|
||||
request.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
@@ -9,6 +9,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
|
@@ -1,33 +1,16 @@
|
||||
using dotnet_etcd;
|
||||
using dotnet_etcd.interfaces;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace DysonNetwork.Pass.Email;
|
||||
|
||||
public class EmailService
|
||||
public class EmailService(
|
||||
PusherService.PusherServiceClient pusher,
|
||||
RazorViewRenderer viewRenderer,
|
||||
ILogger<EmailService> logger
|
||||
)
|
||||
{
|
||||
private readonly PusherService.PusherServiceClient _client;
|
||||
private readonly RazorViewRenderer _viewRenderer;
|
||||
private readonly ILogger<EmailService> _logger;
|
||||
|
||||
public EmailService(
|
||||
EtcdClient etcd,
|
||||
RazorViewRenderer viewRenderer,
|
||||
IConfiguration configuration,
|
||||
ILogger<EmailService> logger,
|
||||
PusherService.PusherServiceClient client
|
||||
)
|
||||
{
|
||||
_client = GrpcClientHelper.CreatePusherServiceClient(
|
||||
etcd,
|
||||
configuration["Service:CertPath"]!,
|
||||
configuration["Service:KeyPath"]!
|
||||
).GetAwaiter().GetResult();
|
||||
_viewRenderer = viewRenderer;
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(
|
||||
string? recipientName,
|
||||
string recipientEmail,
|
||||
@@ -37,7 +20,7 @@ public class EmailService
|
||||
{
|
||||
subject = $"[Solarpass] {subject}";
|
||||
|
||||
await _client.SendEmailAsync(
|
||||
await pusher.SendEmailAsync(
|
||||
new SendEmailRequest()
|
||||
{
|
||||
Email = new EmailMessage()
|
||||
@@ -57,12 +40,12 @@ public class EmailService
|
||||
{
|
||||
try
|
||||
{
|
||||
var htmlBody = await _viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model);
|
||||
var htmlBody = await viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model);
|
||||
await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogError(err, "Failed to render email template...");
|
||||
logger.LogError(err, "Failed to render email template...");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
1977
DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs
generated
Normal file
1977
DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
998
DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs
Normal file
998
DysonNetwork.Pass/Migrations/20250713121237_InitialMigration.cs
Normal file
@@ -0,0 +1,998 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Developer;
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NetTopologySuite.Geometries;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "accounts",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
nick = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
language = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_superuser = table.Column<bool>(type: "boolean", 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: "custom_apps",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true),
|
||||
oauth_config = table.Column<CustomAppOauthConfig>(type: "jsonb", nullable: true),
|
||||
links = table.Column<CustomAppLinks>(type: "jsonb", nullable: true),
|
||||
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_custom_apps", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "permission_groups",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, 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_permission_groups", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_coupons",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
code = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
discount_amount = table.Column<decimal>(type: "numeric", nullable: true),
|
||||
discount_rate = table.Column<double>(type: "double precision", nullable: true),
|
||||
max_usage = table.Column<int>(type: "integer", nullable: true),
|
||||
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_wallet_coupons", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "abuse_reports",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
resolved_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
resolution = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_abuse_reports", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_abuse_reports_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "account_auth_factors",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
secret = table.Column<string>(type: "character varying(8196)", maxLength: 8196, nullable: true),
|
||||
config = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
trustworthy = table.Column<int>(type: "integer", nullable: false),
|
||||
enabled_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_check_in_results",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
level = table.Column<int>(type: "integer", nullable: false),
|
||||
reward_points = table.Column<decimal>(type: "numeric", nullable: true),
|
||||
reward_experience = table.Column<int>(type: "integer", nullable: true),
|
||||
tips = table.Column<ICollection<FortuneTip>>(type: "jsonb", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_check_in_results", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_account_check_in_results_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "account_connections",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
provider = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
provided_identifier = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
access_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
refresh_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_connections", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_account_connections_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<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_primary = table.Column<bool>(type: "boolean", nullable: false),
|
||||
content = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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.CreateTable(
|
||||
name: "account_profiles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
first_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
middle_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
last_name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
bio = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
gender = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
pronouns = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
time_zone = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
location = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
birthday = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
last_seen_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
verification = table.Column<VerificationMark>(type: "jsonb", nullable: true),
|
||||
active_badge = table.Column<BadgeReferenceObject>(type: "jsonb", nullable: true),
|
||||
experience = table.Column<int>(type: "integer", nullable: false),
|
||||
picture_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
background_id = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
picture = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<CloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_profiles", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_account_profiles_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "account_relationships",
|
||||
columns: table => new
|
||||
{
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
related_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
status = table.Column<short>(type: "smallint", 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_relationships", x => new { x.account_id, x.related_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_account_relationships_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_account_relationships_accounts_related_id",
|
||||
column: x => x.related_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "account_statuses",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
attitude = table.Column<int>(type: "integer", nullable: false),
|
||||
is_invisible = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_not_disturb = table.Column<bool>(type: "boolean", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
cleared_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_statuses", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_account_statuses_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "action_logs",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
action = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
||||
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
location = table.Column<Point>(type: "geometry", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
session_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_action_logs", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_action_logs_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_challenges",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
step_remain = table.Column<int>(type: "integer", nullable: false),
|
||||
step_total = table.Column<int>(type: "integer", nullable: false),
|
||||
failed_attempts = table.Column<int>(type: "integer", nullable: false),
|
||||
platform = table.Column<int>(type: "integer", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
blacklist_factors = table.Column<List<Guid>>(type: "jsonb", nullable: false),
|
||||
audiences = table.Column<List<string>>(type: "jsonb", nullable: false),
|
||||
scopes = table.Column<List<string>>(type: "jsonb", nullable: false),
|
||||
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
nonce = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
location = table.Column<Point>(type: "geometry", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_auth_challenges", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_challenges_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "badges",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
caption = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
||||
activated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_badges", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_badges_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "magic_spells",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
spell = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
expires_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_magic_spells", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_magic_spells_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notification_push_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
provider = table.Column<int>(type: "integer", nullable: false),
|
||||
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_notification_push_subscriptions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notification_push_subscriptions_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notifications",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
priority = table.Column<int>(type: "integer", nullable: false),
|
||||
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_notifications", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notifications_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_wallets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallets_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_app_secrets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_oidc = table.Column<bool>(type: "boolean", nullable: false),
|
||||
app_id = table.Column<Guid>(type: "uuid", 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_custom_app_secrets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_custom_app_secrets_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "permission_group_members",
|
||||
columns: table => new
|
||||
{
|
||||
group_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
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_permission_group_members", x => new { x.group_id, x.actor });
|
||||
table.ForeignKey(
|
||||
name: "fk_permission_group_members_permission_groups_group_id",
|
||||
column: x => x.group_id,
|
||||
principalTable: "permission_groups",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "permission_nodes",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
actor = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
area = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
key = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
value = table.Column<JsonDocument>(type: "jsonb", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
affected_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
group_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_permission_nodes", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_permission_nodes_permission_groups_group_id",
|
||||
column: x => x.group_id,
|
||||
principalTable: "permission_groups",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
begun_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
ended_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
is_active = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_free_trial = table.Column<bool>(type: "boolean", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
payment_method = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
payment_details = table.Column<PaymentDetails>(type: "jsonb", nullable: false),
|
||||
base_price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
coupon_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
renewal_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_wallet_subscriptions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_subscriptions_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_subscriptions_wallet_coupons_coupon_id",
|
||||
column: x => x.coupon_id,
|
||||
principalTable: "wallet_coupons",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_sessions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
last_granted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
challenge_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
app_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_auth_sessions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_auth_challenges_challenge_id",
|
||||
column: x => x.challenge_id,
|
||||
principalTable: "auth_challenges",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "payment_transactions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
payer_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_payment_transactions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_payment_transactions_wallets_payee_wallet_id",
|
||||
column: x => x.payee_wallet_id,
|
||||
principalTable: "wallets",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_payment_transactions_wallets_payer_wallet_id",
|
||||
column: x => x.payer_wallet_id,
|
||||
principalTable: "wallets",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "wallet_pockets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
wallet_id = table.Column<Guid>(type: "uuid", 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_wallet_pockets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_wallet_pockets_wallets_wallet_id",
|
||||
column: x => x.wallet_id,
|
||||
principalTable: "wallets",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "payment_orders",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
app_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
transaction_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
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_payment_orders", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_payment_orders_payment_transactions_transaction_id",
|
||||
column: x => x.transaction_id,
|
||||
principalTable: "payment_transactions",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_payment_orders_wallets_payee_wallet_id",
|
||||
column: x => x.payee_wallet_id,
|
||||
principalTable: "wallets",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_abuse_reports_account_id",
|
||||
table: "abuse_reports",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_auth_factors_account_id",
|
||||
table: "account_auth_factors",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_check_in_results_account_id",
|
||||
table: "account_check_in_results",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_connections_account_id",
|
||||
table: "account_connections",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_contacts_account_id",
|
||||
table: "account_contacts",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_profiles_account_id",
|
||||
table: "account_profiles",
|
||||
column: "account_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_relationships_related_id",
|
||||
table: "account_relationships",
|
||||
column: "related_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_account_statuses_account_id",
|
||||
table: "account_statuses",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_accounts_name",
|
||||
table: "accounts",
|
||||
column: "name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_action_logs_account_id",
|
||||
table: "action_logs",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_account_id",
|
||||
table: "auth_challenges",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_account_id",
|
||||
table: "auth_sessions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_challenge_id",
|
||||
table: "auth_sessions",
|
||||
column: "challenge_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_badges_account_id",
|
||||
table: "badges",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_custom_app_secrets_app_id",
|
||||
table: "custom_app_secrets",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_magic_spells_account_id",
|
||||
table: "magic_spells",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_magic_spells_spell",
|
||||
table: "magic_spells",
|
||||
column: "spell",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_account_id",
|
||||
table: "notification_push_subscriptions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_device_token_device_id_acco",
|
||||
table: "notification_push_subscriptions",
|
||||
columns: new[] { "device_token", "device_id", "account_id" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notifications_account_id",
|
||||
table: "notifications",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_payment_orders_payee_wallet_id",
|
||||
table: "payment_orders",
|
||||
column: "payee_wallet_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_payment_orders_transaction_id",
|
||||
table: "payment_orders",
|
||||
column: "transaction_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_payment_transactions_payee_wallet_id",
|
||||
table: "payment_transactions",
|
||||
column: "payee_wallet_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_payment_transactions_payer_wallet_id",
|
||||
table: "payment_transactions",
|
||||
column: "payer_wallet_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_permission_nodes_group_id",
|
||||
table: "permission_nodes",
|
||||
column: "group_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_permission_nodes_key_area_actor",
|
||||
table: "permission_nodes",
|
||||
columns: new[] { "key", "area", "actor" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_pockets_wallet_id",
|
||||
table: "wallet_pockets",
|
||||
column: "wallet_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_account_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_coupon_id",
|
||||
table: "wallet_subscriptions",
|
||||
column: "coupon_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallet_subscriptions_identifier",
|
||||
table: "wallet_subscriptions",
|
||||
column: "identifier");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_wallets_account_id",
|
||||
table: "wallets",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "abuse_reports");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_auth_factors");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_check_in_results");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_connections");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_contacts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_profiles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_relationships");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_statuses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "action_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "badges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_app_secrets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "magic_spells");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notification_push_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "payment_orders");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "permission_group_members");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "permission_nodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_pockets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_apps");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "payment_transactions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "permission_groups");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallet_coupons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "wallets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "accounts");
|
||||
}
|
||||
}
|
||||
}
|
1974
DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs
Normal file
1974
DysonNetwork.Pass/Migrations/AppDatabaseModelSnapshot.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ namespace DysonNetwork.Pass.Permission;
|
||||
|
||||
using System;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RequiredPermissionAttribute(string area, string key) : Attribute
|
||||
{
|
||||
public string Area { get; set; } = area;
|
||||
|
@@ -28,8 +28,6 @@ builder.Services.AddAppBusinessServices(builder.Configuration);
|
||||
// Add scheduled jobs
|
||||
builder.Services.AddAppScheduledJobs();
|
||||
|
||||
builder.Services.AddHostedService<ServiceRegistrationHostedService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Run database migrations
|
||||
|
@@ -19,6 +19,7 @@ using DysonNetwork.Pass.Handlers;
|
||||
using DysonNetwork.Pass.Wallet.PaymentHandlers;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
|
||||
namespace DysonNetwork.Pass.Startup;
|
||||
|
||||
@@ -48,9 +49,8 @@ public static class ServiceCollectionExtensions
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
|
||||
// Register gRPC reflection for service discovery
|
||||
services.AddGrpc();
|
||||
|
||||
services.AddPusherService();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddScoped<AccountServiceGrpc>();
|
||||
services.AddScoped<AuthServiceGrpc>();
|
||||
@@ -194,7 +194,6 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<ActionLogService>();
|
||||
services.AddScoped<RelationshipService>();
|
||||
services.AddScoped<MagicSpellService>();
|
||||
services.AddScoped<NotificationService>();
|
||||
services.AddScoped<AuthService>();
|
||||
services.AddScoped<AccountUsernameService>();
|
||||
services.AddScoped<WalletService>();
|
||||
|
@@ -1,56 +0,0 @@
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DysonNetwork.Pass.Startup;
|
||||
|
||||
public class ServiceRegistrationHostedService : IHostedService
|
||||
{
|
||||
private readonly ServiceRegistry _serviceRegistry;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ServiceRegistrationHostedService> _logger;
|
||||
|
||||
public ServiceRegistrationHostedService(
|
||||
ServiceRegistry serviceRegistry,
|
||||
IConfiguration configuration,
|
||||
ILogger<ServiceRegistrationHostedService> logger)
|
||||
{
|
||||
_serviceRegistry = serviceRegistry;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var serviceName = "DysonNetwork.Pass"; // Preset service name
|
||||
var serviceUrl = _configuration["Service:Url"];
|
||||
|
||||
if (string.IsNullOrEmpty(serviceName) || string.IsNullOrEmpty(serviceUrl))
|
||||
{
|
||||
_logger.LogWarning("Service name or URL not configured. Skipping Etcd registration.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl);
|
||||
try
|
||||
{
|
||||
await _serviceRegistry.RegisterService(serviceName, serviceUrl);
|
||||
_logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// The lease will expire automatically if the service stops.
|
||||
// For explicit unregistration, you would implement it here.
|
||||
_logger.LogInformation("Service registration hosted service is stopping.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,17 +1,18 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
||||
|
||||
namespace DysonNetwork.Pass.Wallet;
|
||||
|
||||
public class PaymentService(
|
||||
AppDatabase db,
|
||||
WalletService wat,
|
||||
NotificationService nty,
|
||||
PusherService.PusherServiceClient pusher,
|
||||
IStringLocalizer<NotificationResource> localizer
|
||||
)
|
||||
{
|
||||
@@ -205,16 +206,19 @@ public class PaymentService(
|
||||
var readableOrderId = order.Id.ToString().Replace("-", "")[..8];
|
||||
var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}";
|
||||
|
||||
await nty.SendNotification(
|
||||
account,
|
||||
"wallets.orders.paid",
|
||||
localizer["OrderPaidTitle", $"#{readableOrderId}"],
|
||||
null,
|
||||
localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency,
|
||||
readableOrderRemark],
|
||||
new Dictionary<string, object>()
|
||||
|
||||
await pusher.SendPushNotificationToUserAsync(
|
||||
new SendPushNotificationToUserRequest
|
||||
{
|
||||
["order_id"] = order.Id.ToString()
|
||||
UserId = account.Id.ToString(),
|
||||
Notification = new PushNotification
|
||||
{
|
||||
Topic = "wallets.orders.paid",
|
||||
Title = localizer["OrderPaidTitle", $"#{readableOrderId}"],
|
||||
Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency,
|
||||
readableOrderRemark],
|
||||
IsSavable = false
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@@ -1,11 +1,14 @@
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Pass.Wallet.PaymentHandlers;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
||||
using Duration = NodaTime.Duration;
|
||||
|
||||
namespace DysonNetwork.Pass.Wallet;
|
||||
|
||||
@@ -13,7 +16,7 @@ public class SubscriptionService(
|
||||
AppDatabase db,
|
||||
PaymentService payment,
|
||||
AccountService accounts,
|
||||
NotificationService nty,
|
||||
PusherService.PusherServiceClient pusher,
|
||||
IStringLocalizer<NotificationResource> localizer,
|
||||
IConfiguration configuration,
|
||||
ICacheService cache,
|
||||
@@ -352,15 +355,19 @@ public class SubscriptionService(
|
||||
? subscription.EndedAt.Value.Minus(subscription.BegunAt).Days.ToString()
|
||||
: "infinite";
|
||||
|
||||
await nty.SendNotification(
|
||||
account,
|
||||
"subscriptions.begun",
|
||||
localizer["SubscriptionAppliedTitle", humanReadableName],
|
||||
null,
|
||||
localizer["SubscriptionAppliedBody", duration, humanReadableName],
|
||||
new Dictionary<string, object>()
|
||||
var notification = new PushNotification
|
||||
{
|
||||
Topic = "subscriptions.begun",
|
||||
Title = localizer["SubscriptionAppliedTitle", humanReadableName],
|
||||
Body = localizer["SubscriptionAppliedBody", duration, humanReadableName],
|
||||
IsSavable = false,
|
||||
};
|
||||
notification.Meta.Add("subscription_id", Value.ForString(subscription.Id.ToString()));
|
||||
await pusher.SendPushNotificationToUserAsync(
|
||||
new SendPushNotificationToUserRequest
|
||||
{
|
||||
["subscription_id"] = subscription.Id.ToString(),
|
||||
UserId = account.Id.ToString(),
|
||||
Notification = notification
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@@ -9,8 +9,9 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379"
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379",
|
||||
"Etcd": "etcd.orb.local:2379"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
@@ -36,60 +37,11 @@
|
||||
"AuthorizationCodeLifetime": "00:30:00",
|
||||
"RequireHttpsMetadata": true
|
||||
},
|
||||
"Tus": {
|
||||
"StorePath": "Uploads"
|
||||
},
|
||||
"Storage": {
|
||||
"PreferredRemote": "minio",
|
||||
"Remote": [
|
||||
{
|
||||
"Id": "minio",
|
||||
"Label": "Minio",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network-development",
|
||||
"Endpoint": "localhost:9000",
|
||||
"SecretId": "littlesheep",
|
||||
"SecretKey": "password",
|
||||
"EnabledSigned": true,
|
||||
"EnableSsl": false
|
||||
},
|
||||
{
|
||||
"Id": "cloudflare",
|
||||
"Label": "Cloudflare R2",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network",
|
||||
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
|
||||
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
|
||||
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
|
||||
"EnableSigned": true,
|
||||
"EnableSsl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"Captcha": {
|
||||
"Provider": "cloudflare",
|
||||
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
|
||||
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
|
||||
},
|
||||
"Notifications": {
|
||||
"Topic": "dev.solsynth.solian",
|
||||
"Endpoint": "http://localhost:8088"
|
||||
},
|
||||
"Email": {
|
||||
"Server": "smtp4dev.orb.local",
|
||||
"Port": 25,
|
||||
"UseSsl": false,
|
||||
"Username": "no-reply@mail.solsynth.dev",
|
||||
"Password": "password",
|
||||
"FromAddress": "no-reply@mail.solsynth.dev",
|
||||
"FromName": "Alphabot",
|
||||
"SubjectPrefix": "Solar Network"
|
||||
},
|
||||
"RealtimeChat": {
|
||||
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
|
||||
"ApiKey": "APIs6TiL8wj3A4j",
|
||||
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
|
||||
},
|
||||
"GeoIp": {
|
||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||
},
|
||||
@@ -125,5 +77,14 @@
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
],
|
||||
"Service": {
|
||||
"Name": "DysonNetwork.Pass",
|
||||
"Url": "http://localhost:5216",
|
||||
"ClientCert": "../Certificates/client.crt",
|
||||
"ClientKey": "../Certificates/client.key"
|
||||
},
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
}
|
||||
}
|
||||
|
@@ -20,31 +20,6 @@ public class WebSocketService
|
||||
|
||||
private static readonly ConcurrentDictionary<string, string> ActiveSubscriptions = new(); // deviceId -> chatRoomId
|
||||
|
||||
public void SubscribeToChatRoom(string chatRoomId, string deviceId)
|
||||
{
|
||||
ActiveSubscriptions[deviceId] = chatRoomId;
|
||||
}
|
||||
|
||||
public void UnsubscribeFromChatRoom(string deviceId)
|
||||
{
|
||||
ActiveSubscriptions.TryRemove(deviceId, out _);
|
||||
}
|
||||
|
||||
public bool IsUserSubscribedToChatRoom(string accountId, string chatRoomId)
|
||||
{
|
||||
var userDeviceIds = ActiveConnections.Keys.Where(k => k.AccountId == accountId).Select(k => k.DeviceId);
|
||||
foreach (var deviceId in userDeviceIds)
|
||||
{
|
||||
if (ActiveSubscriptions.TryGetValue(deviceId, out var subscribedChatRoomId) &&
|
||||
subscribedChatRoomId == chatRoomId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryAdd(
|
||||
(string AccountId, string DeviceId) key,
|
||||
WebSocket socket,
|
||||
@@ -67,7 +42,11 @@ public class WebSocketService
|
||||
);
|
||||
data.Cts.Cancel();
|
||||
ActiveConnections.TryRemove(key, out _);
|
||||
UnsubscribeFromChatRoom(key.DeviceId);
|
||||
}
|
||||
|
||||
public bool GetDeviceIsConnected(string deviceId)
|
||||
{
|
||||
return ActiveConnections.Any(c => c.Key.DeviceId == deviceId);
|
||||
}
|
||||
|
||||
public bool GetAccountIsConnected(string accountId)
|
||||
|
@@ -13,7 +13,11 @@
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||
|
151
DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs
generated
Normal file
151
DysonNetwork.Pusher/Migrations/20250713122638_InitialMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,151 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Pusher;
|
||||
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.Pusher.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20250713122638_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pusher.Notification.Notification", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.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<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<string>("Subtitle")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("subtitle");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<string>("Topic")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("topic");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("ViewedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("viewed_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_notifications");
|
||||
|
||||
b.ToTable("notifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<int>("CountDelivered")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("count_delivered");
|
||||
|
||||
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>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<string>("DeviceToken")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("device_token");
|
||||
|
||||
b.Property<Instant?>("LastUsedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_used_at");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("provider");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_push_subscriptions");
|
||||
|
||||
b.HasIndex("AccountId", "DeviceId", "DeletedAt")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at");
|
||||
|
||||
b.ToTable("push_subscriptions", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pusher.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notifications",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
priority = table.Column<int>(type: "integer", nullable: false),
|
||||
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", 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_notifications", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "push_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
device_id = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
device_token = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
provider = table.Column<int>(type: "integer", nullable: false),
|
||||
count_delivered = table.Column<int>(type: "integer", nullable: false),
|
||||
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
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_push_subscriptions", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_push_subscriptions_account_id_device_id_deleted_at",
|
||||
table: "push_subscriptions",
|
||||
columns: new[] { "account_id", "device_id", "deleted_at" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "push_subscriptions");
|
||||
}
|
||||
}
|
||||
}
|
148
DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs
Normal file
148
DysonNetwork.Pusher/Migrations/AppDatabaseModelSnapshot.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Pusher;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pusher.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
partial class AppDatabaseModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pusher.Notification.Notification", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.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<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<string>("Subtitle")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("subtitle");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<string>("Topic")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("topic");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("ViewedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("viewed_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_notifications");
|
||||
|
||||
b.ToTable("notifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Pusher.Notification.PushSubscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<int>("CountDelivered")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("count_delivered");
|
||||
|
||||
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>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("device_id");
|
||||
|
||||
b.Property<string>("DeviceToken")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("device_token");
|
||||
|
||||
b.Property<Instant?>("LastUsedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_used_at");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("provider");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_push_subscriptions");
|
||||
|
||||
b.HasIndex("AccountId", "DeviceId", "DeletedAt")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_push_subscriptions_account_id_device_id_deleted_at");
|
||||
|
||||
b.ToTable("push_subscriptions", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,5 @@ public class Notification : ModelBase
|
||||
public Instant? ViewedAt { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
[JsonIgnore] public Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Pass;
|
||||
using DysonNetwork.Pass.Auth;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using AccountService = DysonNetwork.Shared.Proto.AccountService;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
namespace DysonNetwork.Pusher.Notification;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/notifications")]
|
||||
public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase
|
||||
public class NotificationController(
|
||||
AppDatabase db,
|
||||
PushService nty,
|
||||
AccountService.AccountServiceClient accounts) : ControllerBase
|
||||
{
|
||||
[HttpGet("count")]
|
||||
[Authorize]
|
||||
@@ -19,9 +22,10 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
if (currentUserValue is not Account currentUser) return Unauthorized();
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
|
||||
var count = await db.Notifications
|
||||
.Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null)
|
||||
.Where(s => s.AccountId == accountId && s.ViewedAt == null)
|
||||
.CountAsync();
|
||||
return Ok(count);
|
||||
}
|
||||
@@ -32,24 +36,25 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
[FromQuery] int offset = 0,
|
||||
// The page size set to 5 is to avoid the client pulled the notification
|
||||
// but didn't render it in the screen-viewable region.
|
||||
[FromQuery] int take = 5
|
||||
[FromQuery] int take = 8
|
||||
)
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
if (currentUserValue is not Account currentUser) return Unauthorized();
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
|
||||
var totalCount = await db.Notifications
|
||||
.Where(s => s.AccountId == currentUser.Id)
|
||||
.Where(s => s.AccountId == accountId)
|
||||
.CountAsync();
|
||||
var notifications = await db.Notifications
|
||||
.Where(s => s.AccountId == currentUser.Id)
|
||||
.Where(s => s.AccountId == accountId)
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
await nty.MarkNotificationsViewed(notifications);
|
||||
await nty.MarkNotificationsViewed(notifications.ToList());
|
||||
|
||||
return Ok(notifications);
|
||||
}
|
||||
@@ -57,14 +62,15 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
public class PushNotificationSubscribeRequest
|
||||
{
|
||||
[MaxLength(4096)] public string DeviceToken { get; set; } = null!;
|
||||
public NotificationPushProvider Provider { get; set; }
|
||||
public PushProvider Provider { get; set; }
|
||||
}
|
||||
|
||||
[HttpPut("subscription")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<NotificationPushSubscription>> SubscribeToPushNotification(
|
||||
[FromBody] PushNotificationSubscribeRequest request
|
||||
)
|
||||
public async Task<ActionResult<PushSubscription>>
|
||||
SubscribeToPushNotification(
|
||||
[FromBody] PushNotificationSubscribeRequest request
|
||||
)
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
@@ -74,8 +80,12 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
if (currentSession == null) return Unauthorized();
|
||||
|
||||
var result =
|
||||
await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!,
|
||||
request.DeviceToken);
|
||||
await nty.SubscribeDevice(
|
||||
currentSession.Challenge.DeviceId!,
|
||||
request.DeviceToken,
|
||||
request.Provider,
|
||||
currentUser
|
||||
);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
@@ -90,10 +100,11 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
if (currentUser == null) return Unauthorized();
|
||||
var currentSession = currentSessionValue as AuthSession;
|
||||
if (currentSession == null) return Unauthorized();
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
|
||||
var affectedRows = await db.NotificationPushSubscriptions
|
||||
var affectedRows = await db.PushSubscriptions
|
||||
.Where(s =>
|
||||
s.AccountId == currentUser.Id &&
|
||||
s.AccountId == accountId &&
|
||||
s.DeviceId == currentSession.Challenge.DeviceId
|
||||
).ExecuteDeleteAsync();
|
||||
return Ok(affectedRows);
|
||||
@@ -109,36 +120,11 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
public int Priority { get; set; } = 10;
|
||||
}
|
||||
|
||||
[HttpPost("broadcast")]
|
||||
[Authorize]
|
||||
[RequiredPermission("global", "notifications.broadcast")]
|
||||
public async Task<ActionResult> BroadcastNotification(
|
||||
[FromBody] NotificationRequest request,
|
||||
[FromQuery] bool save = false
|
||||
)
|
||||
{
|
||||
await nty.BroadcastNotification(
|
||||
new Notification
|
||||
{
|
||||
CreatedAt = SystemClock.Instance.GetCurrentInstant(),
|
||||
UpdatedAt = SystemClock.Instance.GetCurrentInstant(),
|
||||
Topic = request.Topic,
|
||||
Title = request.Title,
|
||||
Subtitle = request.Subtitle,
|
||||
Content = request.Content,
|
||||
Meta = request.Meta,
|
||||
Priority = request.Priority,
|
||||
},
|
||||
save
|
||||
);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class NotificationWithAimRequest : NotificationRequest
|
||||
{
|
||||
[Required] public List<Guid> AccountId { get; set; } = null!;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("send")]
|
||||
[Authorize]
|
||||
[RequiredPermission("global", "notifications.send")]
|
||||
@@ -147,7 +133,6 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
[FromQuery] bool save = false
|
||||
)
|
||||
{
|
||||
var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync();
|
||||
await nty.SendNotificationBatch(
|
||||
new Notification
|
||||
{
|
||||
@@ -159,7 +144,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
|
||||
Content = request.Content,
|
||||
Meta = request.Meta,
|
||||
},
|
||||
accounts,
|
||||
request.AccountId,
|
||||
save
|
||||
);
|
||||
return Ok();
|
@@ -12,14 +12,14 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto
|
||||
private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
||||
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
||||
|
||||
public async Task UnsubscribePushNotifications(string deviceId)
|
||||
public async Task UnsubscribeDevice(string deviceId)
|
||||
{
|
||||
await db.PushSubscriptions
|
||||
.Where(s => s.DeviceId == deviceId)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
|
||||
public async Task<PushSubscription> SubscribePushNotification(
|
||||
public async Task<PushSubscription> SubscribeDevice(
|
||||
string deviceId,
|
||||
string deviceToken,
|
||||
PushProvider provider,
|
||||
|
@@ -9,7 +9,7 @@ namespace DysonNetwork.Pusher.Services;
|
||||
|
||||
public class PusherServiceGrpc(
|
||||
EmailService emailService,
|
||||
WebSocketService webSocketService,
|
||||
WebSocketService websocket,
|
||||
PushService pushService
|
||||
) : PusherService.PusherServiceBase
|
||||
{
|
||||
@@ -32,7 +32,7 @@ public class PusherServiceGrpc(
|
||||
Data = request.Packet.Data,
|
||||
ErrorMessage = request.Packet.ErrorMessage
|
||||
};
|
||||
webSocketService.SendPacketToAccount(request.UserId, packet);
|
||||
websocket.SendPacketToAccount(request.UserId, packet);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class PusherServiceGrpc(
|
||||
ErrorMessage = request.Packet.ErrorMessage
|
||||
};
|
||||
foreach (var userId in request.UserIds)
|
||||
webSocketService.SendPacketToAccount(userId, packet);
|
||||
websocket.SendPacketToAccount(userId, packet);
|
||||
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class PusherServiceGrpc(
|
||||
Data = request.Packet.Data,
|
||||
ErrorMessage = request.Packet.ErrorMessage
|
||||
};
|
||||
webSocketService.SendPacketToDevice(request.DeviceId, packet);
|
||||
websocket.SendPacketToDevice(request.DeviceId, packet);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class PusherServiceGrpc(
|
||||
ErrorMessage = request.Packet.ErrorMessage
|
||||
};
|
||||
foreach (var deviceId in request.DeviceIds)
|
||||
webSocketService.SendPacketToDevice(deviceId, packet);
|
||||
websocket.SendPacketToDevice(deviceId, packet);
|
||||
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
@@ -159,4 +159,22 @@ public class PusherServiceGrpc(
|
||||
await pushService.SendNotificationBatch(notification, accounts, request.Notification.IsSavable);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<Empty> UnsubscribePushNotifications(UnsubscribePushNotificationsRequest request, ServerCallContext context)
|
||||
{
|
||||
await pushService.UnsubscribeDevice(request.DeviceId);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override Task<GetWebsocketConnectionStatusResponse> GetWebsocketConnectionStatus(GetWebsocketConnectionStatusRequest request, ServerCallContext context)
|
||||
{
|
||||
var isConnected = request.IdCase switch
|
||||
{
|
||||
GetWebsocketConnectionStatusRequest.IdOneofCase.DeviceId => websocket.GetDeviceIsConnected(request.DeviceId),
|
||||
GetWebsocketConnectionStatusRequest.IdOneofCase.UserId => websocket.GetAccountIsConnected(request.UserId),
|
||||
_ => false
|
||||
};
|
||||
|
||||
return Task.FromResult(new GetWebsocketConnectionStatusResponse { IsConnected = isConnected });
|
||||
}
|
||||
}
|
@@ -33,8 +33,6 @@ public static class ApplicationConfiguration
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers().RequireRateLimiting("fixed");
|
||||
app.MapStaticAssets().RequireRateLimiting("fixed");
|
||||
app.MapRazorPages().RequireRateLimiting("fixed");
|
||||
|
||||
return app;
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using System.Threading.RateLimiting;
|
||||
using dotnet_etcd.interfaces;
|
||||
using DysonNetwork.Pusher.Connection;
|
||||
using DysonNetwork.Pusher.Email;
|
||||
using DysonNetwork.Pusher.Notification;
|
||||
using DysonNetwork.Pusher.Services;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using NodaTime;
|
||||
@@ -44,18 +43,6 @@ public static class ServiceCollectionExtensions
|
||||
// Register gRPC services
|
||||
services.AddScoped<PusherServiceGrpc>();
|
||||
|
||||
// Register AuthService.AuthServiceClient for AuthMiddleware
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var etcdClient = sp.GetRequiredService<IEtcdClient>();
|
||||
var configuration = sp.GetRequiredService<IConfiguration>();
|
||||
var clientCertPath = configuration["ClientCert:Path"];
|
||||
var clientKeyPath = configuration["ClientKey:Path"];
|
||||
var clientCertPassword = configuration["ClientCert:Password"];
|
||||
|
||||
return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword);
|
||||
});
|
||||
|
||||
// Register OIDC services
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
@@ -144,6 +131,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<WebSocketService>();
|
||||
services.AddScoped<EmailService>();
|
||||
services.AddScoped<PushService>();
|
||||
|
||||
|
@@ -1,56 +0,0 @@
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DysonNetwork.Pusher.Startup;
|
||||
|
||||
public class ServiceRegistrationHostedService : IHostedService
|
||||
{
|
||||
private readonly ServiceRegistry _serviceRegistry;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ServiceRegistrationHostedService> _logger;
|
||||
|
||||
public ServiceRegistrationHostedService(
|
||||
ServiceRegistry serviceRegistry,
|
||||
IConfiguration configuration,
|
||||
ILogger<ServiceRegistrationHostedService> logger)
|
||||
{
|
||||
_serviceRegistry = serviceRegistry;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var serviceName = "DysonNetwork.Pusher"; // Preset service name
|
||||
var serviceUrl = _configuration["Service:Url"];
|
||||
|
||||
if (string.IsNullOrEmpty(serviceUrl))
|
||||
{
|
||||
_logger.LogWarning("Service URL not configured. Skipping Etcd registration.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl);
|
||||
try
|
||||
{
|
||||
await _serviceRegistry.RegisterService(serviceName, serviceUrl);
|
||||
_logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// The lease will expire automatically if the service stops.
|
||||
// For explicit unregistration, you would implement it here.
|
||||
_logger.LogInformation("Service registration hosted service is stopping.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5071",
|
||||
"BaseUrl": "http://localhost:5212",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
@@ -9,67 +9,9 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
"Bearer": {
|
||||
"ValidAudiences": [
|
||||
"http://localhost:5071",
|
||||
"https://localhost:7099"
|
||||
],
|
||||
"ValidIssuer": "solar-network"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AuthToken": {
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem"
|
||||
},
|
||||
"OidcProvider": {
|
||||
"IssuerUri": "https://nt.solian.app",
|
||||
"PublicKeyPath": "Keys/PublicKey.pem",
|
||||
"PrivateKeyPath": "Keys/PrivateKey.pem",
|
||||
"AccessTokenLifetime": "01:00:00",
|
||||
"RefreshTokenLifetime": "30.00:00:00",
|
||||
"AuthorizationCodeLifetime": "00:30:00",
|
||||
"RequireHttpsMetadata": true
|
||||
},
|
||||
"Tus": {
|
||||
"StorePath": "Uploads"
|
||||
},
|
||||
"Storage": {
|
||||
"PreferredRemote": "minio",
|
||||
"Remote": [
|
||||
{
|
||||
"Id": "minio",
|
||||
"Label": "Minio",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network-development",
|
||||
"Endpoint": "localhost:9000",
|
||||
"SecretId": "littlesheep",
|
||||
"SecretKey": "password",
|
||||
"EnabledSigned": true,
|
||||
"EnableSsl": false
|
||||
},
|
||||
{
|
||||
"Id": "cloudflare",
|
||||
"Label": "Cloudflare R2",
|
||||
"Region": "auto",
|
||||
"Bucket": "solar-network",
|
||||
"Endpoint": "0a70a6d1b7128888c823359d0008f4e1.r2.cloudflarestorage.com",
|
||||
"SecretId": "8ff5d06c7b1639829d60bc6838a542e6",
|
||||
"SecretKey": "fd58158c5201be16d1872c9209d9cf199421dae3c2f9972f94b2305976580d67",
|
||||
"EnableSigned": true,
|
||||
"EnableSsl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"Captcha": {
|
||||
"Provider": "cloudflare",
|
||||
"ApiKey": "0x4AAAAAABCDUdOujj4feOb_",
|
||||
"ApiSecret": "0x4AAAAAABCDUWABiJQweqlB7tYq-IqIm8U"
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_pusher;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379",
|
||||
"Etcd": "etcd.orb.local:2379"
|
||||
},
|
||||
"Notifications": {
|
||||
"Topic": "dev.solsynth.solian",
|
||||
@@ -85,45 +27,20 @@
|
||||
"FromName": "Alphabot",
|
||||
"SubjectPrefix": "Solar Network"
|
||||
},
|
||||
"RealtimeChat": {
|
||||
"Endpoint": "https://solar-network-im44o8gq.livekit.cloud",
|
||||
"ApiKey": "APIs6TiL8wj3A4j",
|
||||
"ApiSecret": "SffxRneIwTnlHPtEf3zicmmv3LUEl7xXael4PvWZrEhE"
|
||||
},
|
||||
"GeoIp": {
|
||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||
},
|
||||
"Oidc": {
|
||||
"Google": {
|
||||
"ClientId": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Apple": {
|
||||
"ClientId": "dev.solsynth.solian",
|
||||
"TeamId": "W7HPZ53V6B",
|
||||
"KeyId": "B668YP4KBG",
|
||||
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
||||
},
|
||||
"Microsoft": {
|
||||
"ClientId": "YOUR_MICROSOFT_CLIENT_ID",
|
||||
"ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET",
|
||||
"DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT"
|
||||
}
|
||||
},
|
||||
"Payment": {
|
||||
"Auth": {
|
||||
"Afdian": "<token here>"
|
||||
},
|
||||
"Subscriptions": {
|
||||
"Afdian": {
|
||||
"7d17aae23c9611f0b5705254001e7c00": "solian.stellar.primary",
|
||||
"7dfae4743c9611f0b3a55254001e7c00": "solian.stellar.nova",
|
||||
"141713ee3d6211f085b352540025c377": "solian.stellar.supernova"
|
||||
}
|
||||
}
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
]
|
||||
],
|
||||
"Service": {
|
||||
"Name": "DysonNetwork.Pusher",
|
||||
"Url": "http://localhost:5212",
|
||||
"ClientCert": "../Certificates/client.crt",
|
||||
"ClientKey": "../Certificates/client.key"
|
||||
},
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
}
|
||||
}
|
||||
|
@@ -16,10 +16,9 @@ public class DysonTokenAuthHandler(
|
||||
IOptionsMonitor<DysonTokenAuthOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock,
|
||||
AuthService.AuthServiceClient auth
|
||||
)
|
||||
: AuthenticationHandler<DysonTokenAuthOptions>(options, logger, encoder, clock)
|
||||
: AuthenticationHandler<DysonTokenAuthOptions>(options, logger, encoder)
|
||||
{
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
|
72
DysonNetwork.Shared/Auth/PermissionMiddleware.cs
Normal file
72
DysonNetwork.Shared/Auth/PermissionMiddleware.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DysonNetwork.Shared.Auth
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class RequiredPermissionAttribute(string area, string key) : Attribute
|
||||
{
|
||||
public string Area { get; set; } = area;
|
||||
public string Key { get; } = key;
|
||||
}
|
||||
|
||||
public class PermissionMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext httpContext, PermissionService.PermissionServiceClient permissionService, ILogger<PermissionMiddleware> logger)
|
||||
{
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
|
||||
var attr = endpoint?.Metadata
|
||||
.OfType<RequiredPermissionAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (attr != null)
|
||||
{
|
||||
if (httpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await httpContext.Response.WriteAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
// Assuming Account proto has a bool field 'is_superuser' which is generated as 'IsSuperuser'
|
||||
if (currentUser.IsSuperuser)
|
||||
{
|
||||
// Bypass the permission check for performance
|
||||
await next(httpContext);
|
||||
return;
|
||||
}
|
||||
|
||||
var actor = $"user:{currentUser.Id}";
|
||||
|
||||
try
|
||||
{
|
||||
var permResp = await permissionService.HasPermissionAsync(new HasPermissionRequest
|
||||
{
|
||||
Actor = actor,
|
||||
Area = attr.Area,
|
||||
Key = attr.Key
|
||||
});
|
||||
|
||||
if (!permResp.HasPermission)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await httpContext.Response.WriteAsync($"Permission {attr.Area}/{attr.Key} was required.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
logger.LogError(ex, "gRPC call to PermissionService failed while checking permission {Area}/{Key} for actor {Actor}", attr.Area, attr.Key, actor);
|
||||
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
await httpContext.Response.WriteAsync("Error checking permissions.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await next(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,17 +12,20 @@ public static class DysonAuthStartup
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
services.AddSingleton(sp =>
|
||||
services.AddSingleton<AuthService.AuthServiceClient>(sp =>
|
||||
{
|
||||
var etcdClient = sp.GetRequiredService<IEtcdClient>();
|
||||
var config = sp.GetRequiredService<IConfiguration>();
|
||||
var clientCertPath = config["ClientCert:Path"];
|
||||
var clientKeyPath = config["ClientKey:Path"];
|
||||
var clientCertPassword = config["ClientCert:Password"];
|
||||
var clientCertPath = config["Service:ClientCert"];
|
||||
var clientKeyPath = config["Service:ClientKey"];
|
||||
var clientCertPassword = config["Service:CertPassword"];
|
||||
|
||||
return GrpcClientHelper.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword);
|
||||
return GrpcClientHelper
|
||||
.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
});
|
||||
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
|
||||
|
@@ -19,8 +19,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
|
||||
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
@@ -33,17 +33,6 @@ public static class GrpcClientHelper
|
||||
return response.Kvs[0].Value.ToStringUtf8();
|
||||
}
|
||||
|
||||
public static AccountService.AccountServiceClient CreateAccountServiceClient(
|
||||
string url,
|
||||
string clientCertPath,
|
||||
string clientKeyPath,
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
||||
public static async Task<AccountService.AccountServiceClient> CreateAccountServiceClient(
|
||||
IEtcdClient etcdClient,
|
||||
string clientCertPath,
|
||||
@@ -51,22 +40,11 @@ public static class GrpcClientHelper
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "AccountService");
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
|
||||
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
||||
public static AuthService.AuthServiceClient CreateAuthServiceClient(
|
||||
string url,
|
||||
string clientCertPath,
|
||||
string clientKeyPath,
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
||||
public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
|
||||
IEtcdClient etcdClient,
|
||||
string clientCertPath,
|
||||
@@ -74,22 +52,11 @@ public static class GrpcClientHelper
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "AuthService");
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
|
||||
return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
||||
public static PusherService.PusherServiceClient CreatePusherServiceClient(
|
||||
string url,
|
||||
string clientCertPath,
|
||||
string clientKeyPath,
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
||||
public static async Task<PusherService.PusherServiceClient> CreatePusherServiceClient(
|
||||
IEtcdClient etcdClient,
|
||||
string clientCertPath,
|
||||
@@ -97,7 +64,7 @@ public static class GrpcClientHelper
|
||||
string? clientCertPassword = null
|
||||
)
|
||||
{
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "PusherService");
|
||||
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pusher");
|
||||
return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||
clientCertPassword));
|
||||
}
|
||||
|
@@ -195,6 +195,7 @@ message LevelingInfo {
|
||||
service AccountService {
|
||||
// Account Operations
|
||||
rpc GetAccount(GetAccountRequest) returns (Account) {}
|
||||
rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {}
|
||||
rpc CreateAccount(CreateAccountRequest) returns (Account) {}
|
||||
rpc UpdateAccount(UpdateAccountRequest) returns (Account) {}
|
||||
rpc DeleteAccount(DeleteAccountRequest) returns (google.protobuf.Empty) {}
|
||||
@@ -243,6 +244,14 @@ message GetAccountRequest {
|
||||
string id = 1; // Account ID to retrieve
|
||||
}
|
||||
|
||||
message GetAccountBatchRequest {
|
||||
repeated string id = 1; // Account ID to retrieve
|
||||
}
|
||||
|
||||
message GetAccountBatchResponse {
|
||||
repeated Account accounts = 1; // List of accounts
|
||||
}
|
||||
|
||||
message CreateAccountRequest {
|
||||
string name = 1; // Required: Unique username
|
||||
string nick = 2; // Optional: Display name
|
||||
|
@@ -36,6 +36,12 @@ service PusherService {
|
||||
|
||||
// Sends a push notification to a list of users.
|
||||
rpc SendPushNotificationToUsers(SendPushNotificationToUsersRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Unsubscribes a device from push notifications.
|
||||
rpc UnsubscribePushNotifications(UnsubscribePushNotificationsRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Gets the WebSocket connection status for a device or user.
|
||||
rpc GetWebsocketConnectionStatus(GetWebsocketConnectionStatusRequest) returns (GetWebsocketConnectionStatusResponse) {}
|
||||
}
|
||||
|
||||
// Represents an email message.
|
||||
@@ -108,3 +114,18 @@ message SendPushNotificationToUsersRequest {
|
||||
repeated string user_ids = 1;
|
||||
PushNotification notification = 2;
|
||||
}
|
||||
|
||||
message UnsubscribePushNotificationsRequest {
|
||||
string device_id = 1;
|
||||
}
|
||||
|
||||
message GetWebsocketConnectionStatusRequest {
|
||||
oneof id {
|
||||
string device_id = 1;
|
||||
string user_id = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GetWebsocketConnectionStatusResponse {
|
||||
bool is_connected = 1;
|
||||
}
|
||||
|
@@ -11,10 +11,17 @@ public class RegistryHostedService(
|
||||
)
|
||||
: IHostedService
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var serviceName = configuration["Service:Name"];
|
||||
var serviceUrl = configuration["Service:Url"];
|
||||
var insecure = configuration.GetValue<bool>("Etcd:Insecure");
|
||||
var remote = configuration.GetConnectionString("Etcd");
|
||||
|
||||
if (insecure)
|
||||
logger.LogWarning("Etcd is configured to use insecure channel.");
|
||||
|
||||
if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName))
|
||||
{
|
||||
@@ -22,10 +29,16 @@ public class RegistryHostedService(
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Registering service {ServiceName} at {ServiceUrl} with Etcd.", serviceName, serviceUrl);
|
||||
logger.LogInformation(
|
||||
"Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).",
|
||||
serviceName,
|
||||
serviceUrl,
|
||||
remote
|
||||
);
|
||||
try
|
||||
{
|
||||
await serviceRegistry.RegisterService(serviceName, serviceUrl);
|
||||
_cts = new CancellationTokenSource();
|
||||
await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token);
|
||||
logger.LogInformation("Service {ServiceName} registered successfully.", serviceName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -36,6 +49,8 @@ public class RegistryHostedService(
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
|
||||
// The lease will expire automatically if the service stops ungracefully.
|
||||
var serviceName = configuration["Service:Name"];
|
||||
if (serviceName is not null)
|
||||
|
47
DysonNetwork.Shared/Registry/ServiceHelper.cs
Normal file
47
DysonNetwork.Shared/Registry/ServiceHelper.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using dotnet_etcd.interfaces;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DysonNetwork.Shared.Registry;
|
||||
|
||||
public static class ServiceHelper
|
||||
{
|
||||
public static IServiceCollection AddPusherService(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<PusherService.PusherServiceClient>(sp =>
|
||||
{
|
||||
var etcdClient = sp.GetRequiredService<IEtcdClient>();
|
||||
var config = sp.GetRequiredService<IConfiguration>();
|
||||
var clientCertPath = config["Service:ClientCert"];
|
||||
var clientKeyPath = config["Service:ClientKey"];
|
||||
var clientCertPassword = config["Service:CertPassword"];
|
||||
|
||||
return GrpcClientHelper
|
||||
.CreatePusherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAccountService(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<AccountService.AccountServiceClient>(sp =>
|
||||
{
|
||||
var etcdClient = sp.GetRequiredService<IEtcdClient>();
|
||||
var config = sp.GetRequiredService<IConfiguration>();
|
||||
var clientCertPath = config["Service:ClientCert"];
|
||||
var clientKeyPath = config["Service:ClientKey"];
|
||||
var clientCertPassword = config["Service:CertPassword"];
|
||||
|
||||
return GrpcClientHelper
|
||||
.CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@@ -2,22 +2,38 @@ using System.Text;
|
||||
using dotnet_etcd.interfaces;
|
||||
using Etcdserverpb;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DysonNetwork.Shared.Registry;
|
||||
|
||||
public class ServiceRegistry(IEtcdClient etcd)
|
||||
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
|
||||
{
|
||||
public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60)
|
||||
public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = $"/services/{serviceName}";
|
||||
var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds });
|
||||
var leaseResponse = await etcd.LeaseGrantAsync(
|
||||
new LeaseGrantRequest { TTL = leaseTtlSeconds },
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
await etcd.PutAsync(new PutRequest
|
||||
{
|
||||
Key = ByteString.CopyFrom(key, Encoding.UTF8),
|
||||
Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8),
|
||||
Lease = leaseResponse.ID
|
||||
});
|
||||
await etcd.LeaseKeepAlive(leaseResponse.ID, CancellationToken.None);
|
||||
}, cancellationToken: cancellationToken);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError($"Lease keep-alive failed: {ex.Message}");
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UnregisterService(string serviceName)
|
||||
|
@@ -190,7 +190,6 @@ public class NotificationService(
|
||||
Content = notification.Content,
|
||||
Meta = notification.Meta,
|
||||
Priority = notification.Priority,
|
||||
Account = x,
|
||||
AccountId = x.Id
|
||||
};
|
||||
return newNotification;
|
||||
|
@@ -30,7 +30,7 @@
|
||||
<PackageReference Include="Markdig" Version="0.41.3" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"FastRetrieve": "localhost:6379",
|
||||
"Etcd": "localhost:2379"
|
||||
"Etcd": "etcd.orb.local:2379"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
|
@@ -3,6 +3,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAny_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003F67_003F87f868e3_003FAny_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSettings_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F0f_003F51443844_003FApnSettings_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentNullException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe6898c1ddf974e16b95b114722270029e55000_003Faf_003F30ff0e5c_003FArgumentNullException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHandler_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1f1354e4dbf943ecb04840af5ff9a527fa20_003F5d_003F1fb111f6_003FAuthenticationHandler_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9b24a56e61ae4d86a9e8ba13482a2db924600_003F5b_003F9e854504_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
@@ -28,6 +29,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADiagnosticServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F47e01f36dea14a23aaea6e0391c1347ace00_003F3c_003F140e6d8b_003FDiagnosticServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADirectory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fde_003F94973e27_003FDirectory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointConventionBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F8a_003F101938e3_003FEndpointConventionBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointHttpContextExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc181aff8c6ec418494a7efcfec578fc154e00_003F81_003F048fd513_003FEndpointHttpContextExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnforcerExtension_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb4a120e56464fc6abd8c30969ef70864ba00_003Fb5_003F180850e0_003FEnforcerExtension_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnforcer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb4a120e56464fc6abd8c30969ef70864ba00_003F47_003F3a6b6c4b_003FEnforcer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkQueryableExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe096e6f12c5d6b49356bc34ff1ea08738f910c0929c9d717c9cba7f44288_003FEntityFrameworkQueryableExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
@@ -81,6 +83,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueryable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F42d8f09d6a294d00a6f49efc989927492fe00_003F4e_003F26d1ee34_003FQueryable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueryable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcbafb95b4df34952928f87356db00c8f2fe00_003F9b_003F8ba036bb_003FQueryable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARazorPage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F81d2924a2bbd4b0c864a1d23cbf5f0893d200_003F5f_003Fc110be1c_003FRazorPage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARepeatedField_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003Fc1_003F67c16263_003FRepeatedField_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResizeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F48_003F0209e410_003FResizeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResourceManagerStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb62f365d06c44ad695ff75960cdf97a2a800_003Fe4_003Ff6ba93b7_003FResourceManagerStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARSA_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fee4f989f6b8042b59b2654fdc188e287243600_003F8b_003F44e5f855_003FRSA_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
Reference in New Issue
Block a user