Implemented with Antigravity.

This commit is contained in:
Creeper Lv
2026-06-01 05:09:20 +10:00
parent aaad155a30
commit e8ab8e0684
38 changed files with 3908 additions and 47 deletions
+91 -32
View File
@@ -1,5 +1,17 @@
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.HttpResults;
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SNote.Server.Data;
using SNote.Server.Endpoints;
using SNote.Server.Security;
namespace SNote.Server;
@@ -7,50 +19,97 @@ public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateSlimBuilder(args);
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
// Add Configuration & DB Context (SQLite)
var dbPath = Path.Combine(AppContext.BaseDirectory, "snote_server.db");
builder.Services.AddDbContext<ServerDbContext>(options =>
options.UseSqlite($"Data Source={dbPath}"));
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
// Register custom security managers & HTTP utilities
builder.Services.AddSingleton<CertificateManager>();
builder.Services.AddSingleton<PeerCache>();
builder.Services.AddSingleton<HttpClient>();
// Register OpenAPI explorer
builder.Services.AddOpenApi();
var app = builder.Build();
// 1. Initialize SQLite Database Schema
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ServerDbContext>();
db.Database.EnsureCreated();
}
// Configure Development Tools
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
Todo[] sampleTodos =
[
new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
];
// 2. Map Modular API Endpoint Classes
app.MapAuthEndpoints();
app.MapNodeEndpoints();
app.MapSyncEndpoints();
var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos)
.WithName("GetTodos");
// Welcome / Healthcheck root
app.MapGet("/", () => Results.Ok(new
{
service = "SNote Server",
status = "Online",
time = DateTime.UtcNow
}));
todosApi.MapGet("/{id}", Results<Ok<Todo>, NotFound> (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? TypedResults.Ok(todo)
: TypedResults.NotFound())
.WithName("GetTodoById");
// 3. Start Background Sync Runner
var hostApplicationLifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
var services = app.Services;
// Dynamic Bootstrapping check on startup
var config = app.Configuration;
var bootstrapUrl = config["BootstrapFromPeer"];
if (!string.IsNullOrEmpty(bootstrapUrl))
{
Task.Run(async () =>
{
// Wait briefly for server startup
await Task.Delay(2000);
using var scope = services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ServerDbContext>();
var certManager = scope.ServiceProvider.GetRequiredService<CertificateManager>();
var client = scope.ServiceProvider.GetRequiredService<HttpClient>();
await SyncEndpoints.BootstrapFromPeerServerAsync(bootstrapUrl, db, certManager, client);
});
}
// Dynamic Handshake: Connect to target destination server and register local address on startup
var destUrl = config["Sync:DestinationServerUrl"];
var localUrl = config["Sync:LocalServerUrl"];
if (!string.IsNullOrEmpty(destUrl) && !string.IsNullOrEmpty(localUrl))
{
Task.Run(async () =>
{
// Wait briefly for server startup
await Task.Delay(2000);
using var scope = services.CreateScope();
var certManager = scope.ServiceProvider.GetRequiredService<CertificateManager>();
var client = scope.ServiceProvider.GetRequiredService<HttpClient>();
await SyncEndpoints.RegisterPeerWithDestinationAsync(destUrl, localUrl, certManager, client);
});
}
// Heartbeat Loop: Periodic Downstream Heartbeat pinger pings downstream peers every 30 seconds
Task.Run(async () =>
{
var token = hostApplicationLifetime.ApplicationStopping;
using var scope = services.CreateScope();
var peerCache = scope.ServiceProvider.GetRequiredService<PeerCache>();
var certManager = scope.ServiceProvider.GetRequiredService<CertificateManager>();
var client = scope.ServiceProvider.GetRequiredService<HttpClient>();
await SyncEndpoints.StartHeartbeatPingerAsync(peerCache, certManager, client, token);
});
app.Run();
}
}
public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}