64 lines
2.7 KiB
C#
64 lines
2.7 KiB
C#
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|