using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(); builder.Services.AddDbContext(o => o.UseSqlite("Data Source=data/main.db")); builder.Services.AddHostedService(); var app = builder.Build(); try { using var scope = app.Services.CreateScope(); scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); } catch (Exception e) { Console.WriteLine(e); } app.MapStaticAssets(); app.MapGet("shows", (Database db) => db.Shows.Where(c => c.StartDateTime >= DateTime.Now).ToList()); app.MapGet("tz", () => TimeZoneInfo.Local.Id); app.MapGet("/", () => Results.Redirect("/index.html")); app.Run(); class Database(DbContextOptions options) : DbContext(options) { public DbSet Shows { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().HasIndex(p => new { p.Title, p.StartDateTime }) .IsUnique(); base.OnModelCreating(modelBuilder); } } class Show { public int Id { get; set; } public required string Title { get; set; } public required string Scene { get; set; } public required DateTime StartDateTime { get; set; } public required string TicketUrl { get; set; } public required string Type { get; set; } public required string MovieVersion { get; set; } public required string MovieMainVersion { get; set; } public required string[] Tags { get; set; } } class DataWorker(IServiceScopeFactory serviceScopeFactory) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { const int interval = 1000 * 60 * 60; while (!stoppingToken.IsCancellationRequested) { try { await Work(stoppingToken); await Task.Delay(interval, stoppingToken); } catch (Exception e) { Console.WriteLine(e); } } } async Task Work(CancellationToken ct) { using var scope = serviceScopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var http = scope.ServiceProvider.GetRequiredService(); var logger = scope.ServiceProvider.GetRequiredService>(); var refreshData = Environment.GetEnvironmentVariable("VEGADATA_REFRESH_DATA") == "1"; logger.LogInformation("Syncing data. Refresh={RefreshData}", refreshData); var shows = await http.GetFromJsonAsync("https://www.vegascene.no/api/program", ct); var addedCount = 0; if (refreshData) { File.Copy("data/main.db", $"data/main-{Random.Shared.Next(10000)}.db"); db.Shows.RemoveRange(db.Shows); await db.SaveChangesAsync(ct); } var count = await db.Shows.CountAsync(ct); foreach (var show in shows?.Shows ?? []) { if (string.IsNullOrWhiteSpace(show.TicketSaleUrl)) continue; var existing = await db.Shows.AnyAsync(s => s.Title == show.MovieTitle && s.StartDateTime == show.ShowStart, ct); if (existing) continue; logger.LogInformation("Adding show {Title} at {StartDateTime}", show.MovieTitle, show.ShowStart); db.Shows.Add(new Show() { Title = show.MovieTitle, Scene = show.ScreenName, StartDateTime = show.ShowStart, MovieMainVersion = show.MovieMainVersionId, MovieVersion = show.MovieVersionId, TicketUrl = show.TicketSaleUrl, Type = show.ShowType, Tags = show.VersionTags.Select(t => t.Tag).ToArray() }); addedCount++; } await db.SaveChangesAsync(ct); logger.LogInformation("Synced data, old count:{Count}, new count:{AddedCount}", count, addedCount); } class ShowsResponse { public required List Shows { get; set; } } class VegaShow { [JsonPropertyName("screenName")] public required string ScreenName { get; set; } [JsonPropertyName("ticketSaleUrl")] public required string TicketSaleUrl { get; set; } [JsonPropertyName("showType")] public required string ShowType { get; set; } [JsonPropertyName("showStart")] public required DateTime ShowStart { get; set; } [JsonPropertyName("movieVersionId")] public required string MovieVersionId { get; set; } [JsonPropertyName("movieMainVersionId")] public required string MovieMainVersionId { get; set; } [JsonPropertyName("movieTitle")] public required string MovieTitle { get; set; } [JsonPropertyName("versionTags")] public required VersionTag[] VersionTags { get; set; } } public class VersionTag { [JsonPropertyName("tag")] public required string Tag { get; set; } } }