Public Access
Add sync engine: cursors, push/pull, and the advisory-lock ordering proof (M1)
The vault write path. Push is the only way items change — no per-entity POST/PUT/DELETE — so one place enforces revisions, the change log and access control. The concurrency hazard, now proven rather than asserted: bigserial assigns sequence values when the INSERT runs, not at commit, so transaction A can take sequence 5 while B takes 6 and commits first. A reader polling in between sees only 6, advances past 5, and never learns about it. AdvisoryLockOrderingTests reproduces that gap WITHOUT the lock first — otherwise the with-lock test proves nothing, since it would pass just as happily if the interleaving never occurred — then shows pg_advisory_xact_lock removes it, and that 12 concurrent writers produce no gaps. Cursors are opaque and HMAC-tagged, and carry their vault id. 29 unit tests cover the rejections, which are the point: an accepted-but-wrong cursor is silent data loss, strictly worse than an error a client can resync from. Rejected: tampered tag, tampered payload, foreign signing key, a legitimately-issued cursor from another vault, truncation, and hostile input (never throws — cursors come from clients). Push semantics: - 200 even on partial failure, with per-operation status, so one stale item cannot block everything a client queued while offline. - Conflict returns the server's current row for client-side three-way merge. The server cannot merge ciphertext, so never last-writer-wins. - opId receipts make retries exactly-once per operation, not per batch — a client retrying a partially-overlapping batch after a timeout would otherwise double-apply what landed. - A tombstone beats a late upsert, and delete clears hostname/port: leaving the address would keep the server able to resolve a host the user believes they deleted. - Relay field validation mirrors the DB CHECK so a bad request is a clear Invalid rather than a constraint violation surfacing as a 500. Authorization goes through IVaultAccessService, which returns the same answer for "absent" and "forbidden" — distinguishing them is an existence oracle for other tenants' vault ids. Team vaults are explicitly denied until M3 rather than falling through to a permissive default. JIT provisioning keys on (issuer, subject), never email, and handles the concurrent-first-request race via the unique index. Renamed two domain types: Host -> SshHost, because Host collides with Microsoft.Extensions.Hosting.Host in every file of a web project, and SyncChange -> VaultChange to stop it colliding with the Contracts DTO of the same name. Aliasing at every use site would have been permanent friction. Worth noting: `ef migrations has-pending-model-changes` reported clean after those renames even though the snapshot still said "DodoSSH.Domain.Host" — it diffs tables, not CLR type names. The snapshot was regenerated and the emitted DDL diffed against the previous artifacts/schema/v0.1.sql to confirm the rename produced no schema change. Also removed ConfigureAwait(false) from test methods: xUnit1030 flags it as bypassing parallelization limits, which is why MA0004 is suppressed in test projects. Verified: 0 warnings on a clean rebuild, 146 tests pass (up from 122), format clean. Endpoint-level tests are the immediate next step: they need a WireMock OIDC/JWKS stub and real JWT minting, so the "wrong user is denied" matrix does not exist yet for these two routes. The service-layer authorization and the concurrency property are covered.
This commit is contained in:
@@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace DodoSSH.Infrastructure.Configurations;
|
||||
|
||||
/// <summary>Maps <see cref="Host"/>.</summary>
|
||||
public sealed class HostConfiguration : IEntityTypeConfiguration<Host>
|
||||
/// <summary>Maps <see cref="SshHost"/>.</summary>
|
||||
public sealed class HostConfiguration : IEntityTypeConfiguration<SshHost>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Configure(EntityTypeBuilder<Host> builder)
|
||||
public void Configure(EntityTypeBuilder<SshHost> builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
@@ -49,11 +49,11 @@ public sealed class HostConfiguration : IEntityTypeConfiguration<Host>
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Maps <see cref="SyncChange"/>.</summary>
|
||||
public sealed class SyncChangeConfiguration : IEntityTypeConfiguration<SyncChange>
|
||||
/// <summary>Maps <see cref="VaultChange"/>.</summary>
|
||||
public sealed class SyncChangeConfiguration : IEntityTypeConfiguration<VaultChange>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Configure(EntityTypeBuilder<SyncChange> builder)
|
||||
public void Configure(EntityTypeBuilder<VaultChange> builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ public class DodoDbContext(DbContextOptions<DodoDbContext> options) : DbContext(
|
||||
public DbSet<VaultKeyGrant> VaultKeyGrants => Set<VaultKeyGrant>();
|
||||
|
||||
/// <summary>SSH hosts.</summary>
|
||||
public DbSet<Host> Hosts => Set<Host>();
|
||||
public DbSet<SshHost> Hosts => Set<SshHost>();
|
||||
|
||||
/// <summary>The per-vault change log that delta sync reads.</summary>
|
||||
public DbSet<SyncChange> SyncChanges => Set<SyncChange>();
|
||||
public DbSet<VaultChange> VaultChanges => Set<VaultChange>();
|
||||
|
||||
/// <summary>Applied-operation receipts, for exactly-once retries.</summary>
|
||||
public DbSet<SyncOperationReceipt> SyncOperationReceipts => Set<SyncOperationReceipt>();
|
||||
|
||||
+117
-117
@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace DodoSSH.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(DodoDbContext))]
|
||||
[Migration("20260728113419_InitialSchema")]
|
||||
[Migration("20260728125751_InitialSchema")]
|
||||
partial class InitialSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -74,7 +74,71 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
b.ToTable("device", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.Host", b =>
|
||||
modelBuilder.Entity("DodoSSH.Domain.KeyLogEntry", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at_utc");
|
||||
|
||||
b.Property<byte[]>("EncryptionPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("encryption_public_key");
|
||||
|
||||
b.Property<int>("Generation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("generation");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<byte[]>("PreviousHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("previous_hash");
|
||||
|
||||
b.Property<byte[]>("SigningPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("signing_public_key");
|
||||
|
||||
b.Property<byte[]>("StatementSignature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("statement_signature");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_key_log");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_key_log_hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_key_log_user_id");
|
||||
|
||||
b.ToTable("key_log", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
@@ -176,120 +240,6 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.KeyLogEntry", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at_utc");
|
||||
|
||||
b.Property<byte[]>("EncryptionPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("encryption_public_key");
|
||||
|
||||
b.Property<int>("Generation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("generation");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<byte[]>("PreviousHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("previous_hash");
|
||||
|
||||
b.Property<byte[]>("SigningPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("signing_public_key");
|
||||
|
||||
b.Property<byte[]>("StatementSignature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("statement_signature");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_key_log");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_key_log_hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_key_log_user_id");
|
||||
|
||||
b.ToTable("key_log", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SyncChange", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<Guid>("ActorUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("actor_user_id");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("entity_id");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("entity_type");
|
||||
|
||||
b.Property<DateTimeOffset>("OccurredAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("occurred_at_utc");
|
||||
|
||||
b.Property<int>("Operation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("operation");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("revision");
|
||||
|
||||
b.Property<Guid>("VaultId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("vault_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_sync_change");
|
||||
|
||||
b.HasIndex("VaultId", "Sequence")
|
||||
.HasDatabaseName("ix_sync_change_vault_id_sequence");
|
||||
|
||||
b.HasIndex("VaultId", "EntityId", "Sequence")
|
||||
.IsDescending(false, false, true)
|
||||
.HasDatabaseName("ix_sync_change_vault_id_entity_id_sequence");
|
||||
|
||||
b.ToTable("sync_change", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SyncOperationReceipt", b =>
|
||||
{
|
||||
b.Property<Guid>("OperationId")
|
||||
@@ -740,6 +690,56 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.VaultChange", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<Guid>("ActorUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("actor_user_id");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("entity_id");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("entity_type");
|
||||
|
||||
b.Property<DateTimeOffset>("OccurredAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("occurred_at_utc");
|
||||
|
||||
b.Property<int>("Operation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("operation");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("revision");
|
||||
|
||||
b.Property<Guid>("VaultId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("vault_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_sync_change");
|
||||
|
||||
b.HasIndex("VaultId", "Sequence")
|
||||
.HasDatabaseName("ix_sync_change_vault_id_sequence");
|
||||
|
||||
b.HasIndex("VaultId", "EntityId", "Sequence")
|
||||
.IsDescending(false, false, true)
|
||||
.HasDatabaseName("ix_sync_change_vault_id_entity_id_sequence");
|
||||
|
||||
b.ToTable("sync_change", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.VaultKeyGrant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -841,7 +841,7 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.Host", b =>
|
||||
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
||||
{
|
||||
b.HasOne("DodoSSH.Domain.Vault", "Vault")
|
||||
.WithMany("Hosts")
|
||||
@@ -71,7 +71,71 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
b.ToTable("device", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.Host", b =>
|
||||
modelBuilder.Entity("DodoSSH.Domain.KeyLogEntry", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at_utc");
|
||||
|
||||
b.Property<byte[]>("EncryptionPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("encryption_public_key");
|
||||
|
||||
b.Property<int>("Generation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("generation");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<byte[]>("PreviousHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("previous_hash");
|
||||
|
||||
b.Property<byte[]>("SigningPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("signing_public_key");
|
||||
|
||||
b.Property<byte[]>("StatementSignature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("statement_signature");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_key_log");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_key_log_hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_key_log_user_id");
|
||||
|
||||
b.ToTable("key_log", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
@@ -173,120 +237,6 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.KeyLogEntry", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at_utc");
|
||||
|
||||
b.Property<byte[]>("EncryptionPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("encryption_public_key");
|
||||
|
||||
b.Property<int>("Generation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("generation");
|
||||
|
||||
b.Property<byte[]>("Hash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<byte[]>("PreviousHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("previous_hash");
|
||||
|
||||
b.Property<byte[]>("SigningPublicKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("signing_public_key");
|
||||
|
||||
b.Property<byte[]>("StatementSignature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("statement_signature");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_key_log");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_key_log_hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_key_log_user_id");
|
||||
|
||||
b.ToTable("key_log", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SyncChange", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<Guid>("ActorUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("actor_user_id");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("entity_id");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("entity_type");
|
||||
|
||||
b.Property<DateTimeOffset>("OccurredAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("occurred_at_utc");
|
||||
|
||||
b.Property<int>("Operation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("operation");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("revision");
|
||||
|
||||
b.Property<Guid>("VaultId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("vault_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_sync_change");
|
||||
|
||||
b.HasIndex("VaultId", "Sequence")
|
||||
.HasDatabaseName("ix_sync_change_vault_id_sequence");
|
||||
|
||||
b.HasIndex("VaultId", "EntityId", "Sequence")
|
||||
.IsDescending(false, false, true)
|
||||
.HasDatabaseName("ix_sync_change_vault_id_entity_id_sequence");
|
||||
|
||||
b.ToTable("sync_change", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.SyncOperationReceipt", b =>
|
||||
{
|
||||
b.Property<Guid>("OperationId")
|
||||
@@ -737,6 +687,56 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.VaultChange", b =>
|
||||
{
|
||||
b.Property<long>("Sequence")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("sequence");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property<long>("Sequence"));
|
||||
|
||||
b.Property<Guid>("ActorUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("actor_user_id");
|
||||
|
||||
b.Property<Guid>("EntityId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("entity_id");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("entity_type");
|
||||
|
||||
b.Property<DateTimeOffset>("OccurredAtUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("occurred_at_utc");
|
||||
|
||||
b.Property<int>("Operation")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("operation");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("revision");
|
||||
|
||||
b.Property<Guid>("VaultId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("vault_id");
|
||||
|
||||
b.HasKey("Sequence")
|
||||
.HasName("pk_sync_change");
|
||||
|
||||
b.HasIndex("VaultId", "Sequence")
|
||||
.HasDatabaseName("ix_sync_change_vault_id_sequence");
|
||||
|
||||
b.HasIndex("VaultId", "EntityId", "Sequence")
|
||||
.IsDescending(false, false, true)
|
||||
.HasDatabaseName("ix_sync_change_vault_id_entity_id_sequence");
|
||||
|
||||
b.ToTable("sync_change", "dodo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.VaultKeyGrant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -838,7 +838,7 @@ namespace DodoSSH.Infrastructure.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DodoSSH.Domain.Host", b =>
|
||||
modelBuilder.Entity("DodoSSH.Domain.SshHost", b =>
|
||||
{
|
||||
b.HasOne("DodoSSH.Domain.Vault", "Vault")
|
||||
.WithMany("Hosts")
|
||||
|
||||
Reference in New Issue
Block a user