using Microsoft.EntityFrameworkCore; using Nexus.Api.Data; namespace Nexus.Api.Repositories; public sealed class OpenClawConnectionProfileRepository(NexusDbContext db) : IOpenClawConnectionProfileRepository { public Task GetPrimaryAsync( CancellationToken cancellationToken = default) => db.Set() .AsNoTracking() .SingleOrDefaultAsync( profile => profile.ProfileId == OpenClawConnectionProfile.PrimaryProfileId, cancellationToken); public async Task SavePrimaryAsync( OpenClawConnectionProfile profile, int? expectedRevision, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(profile); if (!string.Equals( profile.ProfileId, OpenClawConnectionProfile.PrimaryProfileId, StringComparison.Ordinal)) { throw new ArgumentException("Only the primary OpenClaw profile is supported.", nameof(profile)); } var profiles = db.Set(); var current = await profiles.SingleOrDefaultAsync( item => item.ProfileId == OpenClawConnectionProfile.PrimaryProfileId, cancellationToken); if (current is null) { if (expectedRevision is not null and not 0) { throw new OpenClawConnectionProfileConcurrencyException( "The OpenClaw connection profile no longer exists."); } profile.Revision = 1; profile.CreatedAt = profile.CreatedAt == default ? DateTimeOffset.UtcNow : profile.CreatedAt; profile.UpdatedAt = DateTimeOffset.UtcNow; profiles.Add(profile); await SaveChangesAsync(cancellationToken); return Clone(profile); } if (expectedRevision is null || expectedRevision.Value != current.Revision) { throw new OpenClawConnectionProfileConcurrencyException( "The OpenClaw connection profile changed since it was loaded.", current.Revision); } current.Endpoint = profile.Endpoint; current.DiscoverySource = profile.DiscoverySource; current.RequiredVersion = profile.RequiredVersion; current.TlsCertificateFingerprint = profile.TlsCertificateFingerprint; current.AdoptionState = profile.AdoptionState; current.ManagementEnabled = profile.ManagementEnabled; current.CapabilityHash = profile.CapabilityHash; current.DeviceId = profile.DeviceId; current.LastProbedAt = profile.LastProbedAt; current.LastVerifiedAt = profile.LastVerifiedAt; current.AdoptedAt = profile.AdoptedAt; current.UpdatedAt = DateTimeOffset.UtcNow; current.Revision++; await SaveChangesAsync(cancellationToken); return Clone(current); } public async Task DeletePrimaryAsync( int expectedRevision, CancellationToken cancellationToken = default) { var profiles = db.Set(); var current = await profiles.SingleOrDefaultAsync( item => item.ProfileId == OpenClawConnectionProfile.PrimaryProfileId, cancellationToken); if (current is null) return false; if (current.Revision != expectedRevision) { throw new OpenClawConnectionProfileConcurrencyException( "The OpenClaw connection profile changed since it was loaded.", current.Revision); } profiles.Remove(current); await SaveChangesAsync(cancellationToken); return true; } private async Task SaveChangesAsync(CancellationToken cancellationToken) { try { await db.SaveChangesAsync(cancellationToken); } catch (DbUpdateConcurrencyException exception) { throw new OpenClawConnectionProfileConcurrencyException( "The OpenClaw connection profile changed concurrently.", innerException: exception); } } private static OpenClawConnectionProfile Clone(OpenClawConnectionProfile profile) => new() { ProfileId = profile.ProfileId, Endpoint = profile.Endpoint, DiscoverySource = profile.DiscoverySource, RequiredVersion = profile.RequiredVersion, TlsCertificateFingerprint = profile.TlsCertificateFingerprint, AdoptionState = profile.AdoptionState, ManagementEnabled = profile.ManagementEnabled, CapabilityHash = profile.CapabilityHash, DeviceId = profile.DeviceId, Revision = profile.Revision, CreatedAt = profile.CreatedAt, UpdatedAt = profile.UpdatedAt, LastProbedAt = profile.LastProbedAt, LastVerifiedAt = profile.LastVerifiedAt, AdoptedAt = profile.AdoptedAt }; }