• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

curt / Viae / 19527137380

20 Nov 2025 05:53AM UTC coverage: 51.56%. First build
19527137380

push

github

web-flow
feat: provide minimal locus and vestigium views (#1)

62 of 109 branches covered (56.88%)

Branch coverage included in aggregate %.

500 of 981 new or added lines in 61 files covered. (50.97%)

500 of 981 relevant lines covered (50.97%)

19.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/endpoint/src/Viae.Presentation/Program.cs
1
// Copyright © 2025 Curt Gilman
2
// SPDX-License-Identifier: AGPL-3.0-only
3
// Viae: A geo-centric, journey-focused, federated blog platform
4

5
using System.Text.Json.Serialization;
6
using Fluid;
7
using Fluid.MvcViewEngine;
8
using Hangfire;
9
using Hangfire.PostgreSql;
10
using Markdig;
11
using Microsoft.EntityFrameworkCore;
12
using Microsoft.Extensions.FileProviders;
13
using Viae.ActivityPub;
14
using Viae.Domain.Models;
15
using Viae.Fluid.Markdown.Mvc;
16
using Viae.Persistence;
17
using Viae.Persistence.Extensions;
18
using Viae.Presentation.Converters;
19
using Viae.Services.Extensions;
20

NEW
21
var builder = WebApplication.CreateBuilder(args);
×
NEW
22
var env = builder.Environment;
×
23

24
// Configure and validate PostgreSQL settings
NEW
25
var postgresConfig =
×
NEW
26
    builder.Configuration.GetSection(PostgresConfiguration.SectionName).Get<PostgresConfiguration>()
×
NEW
27
    ?? new PostgresConfiguration();
×
28

NEW
29
var validator = new PostgresConfigurationValidator();
×
NEW
30
var validationResult = validator.Validate(postgresConfig);
×
NEW
31
if (!validationResult.IsValid)
×
32
{
NEW
33
    var errors = string.Join("; ", validationResult.Errors.Select(e => e.ErrorMessage));
×
NEW
34
    throw new InvalidOperationException($"PostgreSQL configuration is invalid: {errors}");
×
35
}
36

NEW
37
var connectionString = postgresConfig.BuildConnectionString();
×
38

39
// Add database context
NEW
40
builder.Services.AddDbContextFactory<ViaeDbContext>(options =>
×
NEW
41
    options.UseNpgsql(
×
NEW
42
        connectionString,
×
NEW
43
        npgsqlOptions =>
×
NEW
44
        {
×
NEW
45
            npgsqlOptions.UseNetTopologySuite();
×
NEW
46
            npgsqlOptions.MapEnum<Origo>();
×
NEW
47
        }
×
NEW
48
    )
×
NEW
49
);
×
50

51
// Add CORS
NEW
52
builder.Services.AddCors(options =>
×
NEW
53
{
×
NEW
54
    options.AddDefaultPolicy(policy =>
×
NEW
55
    {
×
NEW
56
        policy
×
NEW
57
            .WithOrigins(
×
NEW
58
                builder.Configuration.GetValue<string>("Cors:AllowedOrigins")
×
NEW
59
                    ?? "http://localhost:5173"
×
NEW
60
            )
×
NEW
61
            .AllowCredentials() // Required for cookies
×
NEW
62
            .AllowAnyHeader()
×
NEW
63
            .AllowAnyMethod();
×
NEW
64
    });
×
NEW
65
});
×
66

NEW
67
builder
×
NEW
68
    .Services.AddControllersWithViews()
×
NEW
69
    .AddJsonOptions(options =>
×
NEW
70
    {
×
NEW
71
        // Register the Point JSON converter
×
NEW
72
        options.JsonSerializerOptions.Converters.Add(new PointJsonConverter());
×
NEW
73
        options.JsonSerializerOptions.Converters.Add(new OsmTypeJsonConverter());
×
NEW
74
        options.JsonSerializerOptions.Converters.Add(new OsmIdJsonConverter());
×
NEW
75
        options.JsonSerializerOptions.Converters.Add(new ActivityObjectJsonConverter());
×
NEW
76

×
NEW
77
        options.JsonSerializerOptions.NumberHandling =
×
NEW
78
            JsonNumberHandling.AllowNamedFloatingPointLiterals;
×
NEW
79

×
NEW
80
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
×
NEW
81
    })
×
NEW
82
    .AddFluid();
×
83

84
// The configure step comes after adding Fluid.
NEW
85
builder.Services.Configure<FluidMvcViewOptions>(options =>
×
NEW
86
{
×
NEW
87
    var fileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "Mvc", "Views"));
×
NEW
88
    options.TemplateOptions.FileProvider = fileProvider;
×
NEW
89
    options.ViewsFileProvider = fileProvider;
×
NEW
90
    options.PartialsFileProvider = fileProvider;
×
NEW
91

×
NEW
92
    var strategy = new UnsafeMemberAccessStrategy();
×
NEW
93
    options.TemplateOptions.MemberAccessStrategy = strategy;
×
NEW
94

×
NEW
95
    // Add Markdown extensions
×
NEW
96
    options.AddFluidMarkdownFilters(cfg =>
×
NEW
97
        cfg.ConfigurePipeline = b => b.UseAdvancedExtensions().UsePipeTables()
×
NEW
98
    );
×
NEW
99
});
×
100

101
// Add persistence services
NEW
102
builder.Services.AddViaePersistence(builder.Configuration);
×
103

104
// Add other services
NEW
105
builder.Services.AddViaeServices();
×
106

107
// Add Hangfire services
NEW
108
builder.Services.AddHangfire(configuration =>
×
NEW
109
    configuration
×
NEW
110
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
×
NEW
111
        .UseSimpleAssemblyNameTypeSerializer()
×
NEW
112
        .UseRecommendedSerializerSettings()
×
NEW
113
        .UsePostgreSqlStorage(options => options.UseNpgsqlConnection(connectionString))
×
NEW
114
);
×
115

116
// Add the processing server as IHostedService
NEW
117
var defaultQueues = new[] { "critical", "default", "low" };
×
NEW
118
builder.Services.AddHangfireServer(options =>
×
NEW
119
{
×
NEW
120
    options.WorkerCount = builder.Configuration.GetValue("Hangfire:WorkerCount", 5);
×
NEW
121
    options.Queues =
×
NEW
122
        builder.Configuration.GetSection("Hangfire:Queues").Get<string[]>() ?? defaultQueues;
×
NEW
123
});
×
124

NEW
125
var app = builder.Build();
×
126

127
// Enable Hangfire Dashboard
NEW
128
var dashboardPath = builder.Configuration.GetValue("Hangfire:DashboardPath", "/hangfire");
×
NEW
129
app.UseHangfireDashboard(
×
NEW
130
    dashboardPath,
×
NEW
131
    new DashboardOptions
×
NEW
132
    {
×
NEW
133
        // In development, allow anonymous access. In production, add authorization.
×
NEW
134
        Authorization = app.Environment.IsDevelopment()
×
NEW
135
            ? Array.Empty<Hangfire.Dashboard.IDashboardAuthorizationFilter>()
×
NEW
136
            : [new Hangfire.Dashboard.LocalRequestsOnlyAuthorizationFilter()],
×
NEW
137
    }
×
NEW
138
);
×
139

NEW
140
app.UseDefaultFiles();
×
141

NEW
142
app.UseStaticFiles(
×
NEW
143
    new StaticFileOptions()
×
NEW
144
    {
×
NEW
145
        FileProvider = new PhysicalFileProvider(
×
NEW
146
            Path.Combine(env.ContentRootPath, "Mvc", "wwwroot")
×
NEW
147
        ),
×
NEW
148
    }
×
NEW
149
);
×
150

151
// Enable CORS (must be before authentication/authorization)
NEW
152
app.UseCors();
×
153

154
// Add authentication and authorization middleware
NEW
155
app.UseAuthentication();
×
NEW
156
app.UseAuthorization();
×
157

158
// Map controllers
NEW
159
app.MapControllers();
×
160

161
// Apply database migrations on startup
NEW
162
using (var scope = app.Services.CreateScope())
×
163
{
NEW
164
    var dbContextFactory = scope.ServiceProvider.GetRequiredService<
×
NEW
165
        IDbContextFactory<ViaeDbContext>
×
NEW
166
    >();
×
NEW
167
    var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
×
168

NEW
169
    MigrationLog.ApplyingMigrations(logger);
×
170

171
    try
172
    {
NEW
173
        using var dbContext = dbContextFactory.CreateDbContext();
×
NEW
174
        dbContext.Database.Migrate();
×
NEW
175
        MigrationLog.MigrationsAppliedSuccessfully(logger);
×
NEW
176
    }
×
NEW
177
    catch (Exception ex)
×
178
    {
NEW
179
        MigrationLog.MigrationError(logger, ex);
×
NEW
180
        throw; // Fail fast if migrations fail
×
181
    }
182
}
183

NEW
184
app.Run();
×
185

186
// High-performance logging using LoggerMessage source generator
187
internal static partial class MigrationLog
188
{
189
    [LoggerMessage(Level = LogLevel.Information, Message = "Applying database migrations...")]
190
    internal static partial void ApplyingMigrations(ILogger logger);
191

192
    [LoggerMessage(
193
        Level = LogLevel.Information,
194
        Message = "Database migrations applied successfully"
195
    )]
196
    internal static partial void MigrationsAppliedSuccessfully(ILogger logger);
197

198
    [LoggerMessage(Level = LogLevel.Error, Message = "Error applying database migrations")]
199
    internal static partial void MigrationError(ILogger logger, Exception ex);
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc