Files
SocialPub/PrivaPub/Models/ServerSetup.cs
T

64 lines
2.7 KiB
C#
Raw Normal View History

using MongoDB.Driver;
using MongoDB.Entities;
namespace PrivaPub.Models
{
// The server's first-time setup (owner decision 2026-10-16): while no administrator exists, the client's setup page makes
// the first login, which becomes the administrator; this document then closes setup for good, whoever is demoted or
// deleted later (the CLI stays the way back). The first request to insert it wins, so two setups at once make one
// administrator. A backup never holds it and a restore never drops it (ServerBackup.Excluded): a restore tombstones an
// administrator made after the backup, and setup would otherwise be open to whoever came first.
public class ServerSetup : Entity
{
public const string Name = "setup";
public string AdminId { get; set; }//null while the claiming request is still making its login
public DateTime SealedAt { get; set; }
public static async Task<bool> IsPending(CancellationToken token) =>
!await DB.Default.Find<ServerSetup>().Match(s => s.ID == Name).ExecuteAnyAsync(token);
/// <summary>Takes the setup for one request: false when it is done, or another request took it first.</summary>
public static async Task<bool> Claim(CancellationToken token)
{
try
{
await DB.Default.InsertAsync(new ServerSetup { ID = Name, SealedAt = DateTime.UtcNow }, token);
return true;
}
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
return false;
}
}
/// <summary>Names the administrator the claiming request made.</summary>
public static Task Claimed(string adminId, CancellationToken token) =>
DB.Default.Update<ServerSetup>().Match(s => s.ID == Name && s.AdminId == null).Modify(s => s.AdminId, adminId).ExecuteAsync(token);
/// <summary>Gives a claim back when its login couldn't be made; a named administrator is never given back.</summary>
public static Task Release() =>
DB.Default.DeleteAsync<ServerSetup>(s => s.ID == Name && s.AdminId == null);//a delete takes no token outside a transaction
/// <summary>Closes setup for an administrator made elsewhere (the CLI, a server that had one already).</summary>
public static async Task Seal(string adminId, CancellationToken token)
{
try
{
await DB.Default.Update<ServerSetup>().Match(s => s.ID == Name)
.Modify(b => b.SetOnInsert(s => s.AdminId, adminId))
.Modify(b => b.SetOnInsert(s => s.SealedAt, DateTime.UtcNow))
.Option(o => o.IsUpsert = true)
.ExecuteAsync(token);
}
catch (MongoCommandException ex) when (ex.Code == 11000)
{
//sealed meanwhile
}
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
//sealed meanwhile
}
}
}
}