Implemented with Antigravity.
This commit is contained in:
+91
-32
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user