+30
.dockerignore
+30
.dockerignore
···
1
+
**/.classpath
2
+
**/.dockerignore
3
+
**/.env
4
+
**/.git
5
+
**/.gitignore
6
+
**/.project
7
+
**/.settings
8
+
**/.toolstarget
9
+
**/.vs
10
+
**/.vscode
11
+
**/*.*proj.user
12
+
**/*.dbmdl
13
+
**/*.jfm
14
+
**/azds.yaml
15
+
**/bin
16
+
**/charts
17
+
**/docker-compose*
18
+
**/Dockerfile*
19
+
**/node_modules
20
+
**/npm-debug.log
21
+
**/obj
22
+
**/secrets.dev.yaml
23
+
**/values.dev.yaml
24
+
LICENSE
25
+
README.md
26
+
!**/.gitignore
27
+
!.git/HEAD
28
+
!.git/config
29
+
!.git/packed-refs
30
+
!.git/refs/heads/**
+484
.gitignore
+484
.gitignore
···
1
+
## Ignore Visual Studio temporary files, build results, and
2
+
## files generated by popular Visual Studio add-ons.
3
+
##
4
+
## Get latest from `dotnet new gitignore`
5
+
6
+
# dotenv files
7
+
.env
8
+
9
+
# User-specific files
10
+
*.rsuser
11
+
*.suo
12
+
*.user
13
+
*.userosscache
14
+
*.sln.docstates
15
+
16
+
# User-specific files (MonoDevelop/Xamarin Studio)
17
+
*.userprefs
18
+
19
+
# Mono auto generated files
20
+
mono_crash.*
21
+
22
+
# Build results
23
+
[Dd]ebug/
24
+
[Dd]ebugPublic/
25
+
[Rr]elease/
26
+
[Rr]eleases/
27
+
x64/
28
+
x86/
29
+
[Ww][Ii][Nn]32/
30
+
[Aa][Rr][Mm]/
31
+
[Aa][Rr][Mm]64/
32
+
bld/
33
+
[Bb]in/
34
+
[Oo]bj/
35
+
[Ll]og/
36
+
[Ll]ogs/
37
+
38
+
# Visual Studio 2015/2017 cache/options directory
39
+
.vs/
40
+
# Uncomment if you have tasks that create the project's static files in wwwroot
41
+
#wwwroot/
42
+
43
+
# Visual Studio 2017 auto generated files
44
+
Generated\ Files/
45
+
46
+
# MSTest test Results
47
+
[Tt]est[Rr]esult*/
48
+
[Bb]uild[Ll]og.*
49
+
50
+
# NUnit
51
+
*.VisualState.xml
52
+
TestResult.xml
53
+
nunit-*.xml
54
+
55
+
# Build Results of an ATL Project
56
+
[Dd]ebugPS/
57
+
[Rr]eleasePS/
58
+
dlldata.c
59
+
60
+
# Benchmark Results
61
+
BenchmarkDotNet.Artifacts/
62
+
63
+
# .NET
64
+
project.lock.json
65
+
project.fragment.lock.json
66
+
artifacts/
67
+
68
+
# Tye
69
+
.tye/
70
+
71
+
# ASP.NET Scaffolding
72
+
ScaffoldingReadMe.txt
73
+
74
+
# StyleCop
75
+
StyleCopReport.xml
76
+
77
+
# Files built by Visual Studio
78
+
*_i.c
79
+
*_p.c
80
+
*_h.h
81
+
*.ilk
82
+
*.meta
83
+
*.obj
84
+
*.iobj
85
+
*.pch
86
+
*.pdb
87
+
*.ipdb
88
+
*.pgc
89
+
*.pgd
90
+
*.rsp
91
+
*.sbr
92
+
*.tlb
93
+
*.tli
94
+
*.tlh
95
+
*.tmp
96
+
*.tmp_proj
97
+
*_wpftmp.csproj
98
+
*.log
99
+
*.tlog
100
+
*.vspscc
101
+
*.vssscc
102
+
.builds
103
+
*.pidb
104
+
*.svclog
105
+
*.scc
106
+
107
+
# Chutzpah Test files
108
+
_Chutzpah*
109
+
110
+
# Visual C++ cache files
111
+
ipch/
112
+
*.aps
113
+
*.ncb
114
+
*.opendb
115
+
*.opensdf
116
+
*.sdf
117
+
*.cachefile
118
+
*.VC.db
119
+
*.VC.VC.opendb
120
+
121
+
# Visual Studio profiler
122
+
*.psess
123
+
*.vsp
124
+
*.vspx
125
+
*.sap
126
+
127
+
# Visual Studio Trace Files
128
+
*.e2e
129
+
130
+
# TFS 2012 Local Workspace
131
+
$tf/
132
+
133
+
# Guidance Automation Toolkit
134
+
*.gpState
135
+
136
+
# ReSharper is a .NET coding add-in
137
+
_ReSharper*/
138
+
*.[Rr]e[Ss]harper
139
+
*.DotSettings.user
140
+
141
+
# TeamCity is a build add-in
142
+
_TeamCity*
143
+
144
+
# DotCover is a Code Coverage Tool
145
+
*.dotCover
146
+
147
+
# AxoCover is a Code Coverage Tool
148
+
.axoCover/*
149
+
!.axoCover/settings.json
150
+
151
+
# Coverlet is a free, cross platform Code Coverage Tool
152
+
coverage*.json
153
+
coverage*.xml
154
+
coverage*.info
155
+
156
+
# Visual Studio code coverage results
157
+
*.coverage
158
+
*.coveragexml
159
+
160
+
# NCrunch
161
+
_NCrunch_*
162
+
.*crunch*.local.xml
163
+
nCrunchTemp_*
164
+
165
+
# MightyMoose
166
+
*.mm.*
167
+
AutoTest.Net/
168
+
169
+
# Web workbench (sass)
170
+
.sass-cache/
171
+
172
+
# Installshield output folder
173
+
[Ee]xpress/
174
+
175
+
# DocProject is a documentation generator add-in
176
+
DocProject/buildhelp/
177
+
DocProject/Help/*.HxT
178
+
DocProject/Help/*.HxC
179
+
DocProject/Help/*.hhc
180
+
DocProject/Help/*.hhk
181
+
DocProject/Help/*.hhp
182
+
DocProject/Help/Html2
183
+
DocProject/Help/html
184
+
185
+
# Click-Once directory
186
+
publish/
187
+
188
+
# Publish Web Output
189
+
*.[Pp]ublish.xml
190
+
*.azurePubxml
191
+
# Note: Comment the next line if you want to checkin your web deploy settings,
192
+
# but database connection strings (with potential passwords) will be unencrypted
193
+
*.pubxml
194
+
*.publishproj
195
+
196
+
# Microsoft Azure Web App publish settings. Comment the next line if you want to
197
+
# checkin your Azure Web App publish settings, but sensitive information contained
198
+
# in these scripts will be unencrypted
199
+
PublishScripts/
200
+
201
+
# NuGet Packages
202
+
*.nupkg
203
+
# NuGet Symbol Packages
204
+
*.snupkg
205
+
# The packages folder can be ignored because of Package Restore
206
+
**/[Pp]ackages/*
207
+
# except build/, which is used as an MSBuild target.
208
+
!**/[Pp]ackages/build/
209
+
# Uncomment if necessary however generally it will be regenerated when needed
210
+
#!**/[Pp]ackages/repositories.config
211
+
# NuGet v3's project.json files produces more ignorable files
212
+
*.nuget.props
213
+
*.nuget.targets
214
+
215
+
# Microsoft Azure Build Output
216
+
csx/
217
+
*.build.csdef
218
+
219
+
# Microsoft Azure Emulator
220
+
ecf/
221
+
rcf/
222
+
223
+
# Windows Store app package directories and files
224
+
AppPackages/
225
+
BundleArtifacts/
226
+
Package.StoreAssociation.xml
227
+
_pkginfo.txt
228
+
*.appx
229
+
*.appxbundle
230
+
*.appxupload
231
+
232
+
# Visual Studio cache files
233
+
# files ending in .cache can be ignored
234
+
*.[Cc]ache
235
+
# but keep track of directories ending in .cache
236
+
!?*.[Cc]ache/
237
+
238
+
# Others
239
+
ClientBin/
240
+
~$*
241
+
*~
242
+
*.dbmdl
243
+
*.dbproj.schemaview
244
+
*.jfm
245
+
*.pfx
246
+
*.publishsettings
247
+
orleans.codegen.cs
248
+
249
+
# Including strong name files can present a security risk
250
+
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
251
+
#*.snk
252
+
253
+
# Since there are multiple workflows, uncomment next line to ignore bower_components
254
+
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255
+
#bower_components/
256
+
257
+
# RIA/Silverlight projects
258
+
Generated_Code/
259
+
260
+
# Backup & report files from converting an old project file
261
+
# to a newer Visual Studio version. Backup files are not needed,
262
+
# because we have git ;-)
263
+
_UpgradeReport_Files/
264
+
Backup*/
265
+
UpgradeLog*.XML
266
+
UpgradeLog*.htm
267
+
ServiceFabricBackup/
268
+
*.rptproj.bak
269
+
270
+
# SQL Server files
271
+
*.mdf
272
+
*.ldf
273
+
*.ndf
274
+
275
+
# Business Intelligence projects
276
+
*.rdl.data
277
+
*.bim.layout
278
+
*.bim_*.settings
279
+
*.rptproj.rsuser
280
+
*- [Bb]ackup.rdl
281
+
*- [Bb]ackup ([0-9]).rdl
282
+
*- [Bb]ackup ([0-9][0-9]).rdl
283
+
284
+
# Microsoft Fakes
285
+
FakesAssemblies/
286
+
287
+
# GhostDoc plugin setting file
288
+
*.GhostDoc.xml
289
+
290
+
# Node.js Tools for Visual Studio
291
+
.ntvs_analysis.dat
292
+
node_modules/
293
+
294
+
# Visual Studio 6 build log
295
+
*.plg
296
+
297
+
# Visual Studio 6 workspace options file
298
+
*.opt
299
+
300
+
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301
+
*.vbw
302
+
303
+
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
304
+
*.vbp
305
+
306
+
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
307
+
*.dsw
308
+
*.dsp
309
+
310
+
# Visual Studio 6 technical files
311
+
*.ncb
312
+
*.aps
313
+
314
+
# Visual Studio LightSwitch build output
315
+
**/*.HTMLClient/GeneratedArtifacts
316
+
**/*.DesktopClient/GeneratedArtifacts
317
+
**/*.DesktopClient/ModelManifest.xml
318
+
**/*.Server/GeneratedArtifacts
319
+
**/*.Server/ModelManifest.xml
320
+
_Pvt_Extensions
321
+
322
+
# Paket dependency manager
323
+
.paket/paket.exe
324
+
paket-files/
325
+
326
+
# FAKE - F# Make
327
+
.fake/
328
+
329
+
# CodeRush personal settings
330
+
.cr/personal
331
+
332
+
# Python Tools for Visual Studio (PTVS)
333
+
__pycache__/
334
+
*.pyc
335
+
336
+
# Cake - Uncomment if you are using it
337
+
# tools/**
338
+
# !tools/packages.config
339
+
340
+
# Tabs Studio
341
+
*.tss
342
+
343
+
# Telerik's JustMock configuration file
344
+
*.jmconfig
345
+
346
+
# BizTalk build output
347
+
*.btp.cs
348
+
*.btm.cs
349
+
*.odx.cs
350
+
*.xsd.cs
351
+
352
+
# OpenCover UI analysis results
353
+
OpenCover/
354
+
355
+
# Azure Stream Analytics local run output
356
+
ASALocalRun/
357
+
358
+
# MSBuild Binary and Structured Log
359
+
*.binlog
360
+
361
+
# NVidia Nsight GPU debugger configuration file
362
+
*.nvuser
363
+
364
+
# MFractors (Xamarin productivity tool) working folder
365
+
.mfractor/
366
+
367
+
# Local History for Visual Studio
368
+
.localhistory/
369
+
370
+
# Visual Studio History (VSHistory) files
371
+
.vshistory/
372
+
373
+
# BeatPulse healthcheck temp database
374
+
healthchecksdb
375
+
376
+
# Backup folder for Package Reference Convert tool in Visual Studio 2017
377
+
MigrationBackup/
378
+
379
+
# Ionide (cross platform F# VS Code tools) working folder
380
+
.ionide/
381
+
382
+
# Fody - auto-generated XML schema
383
+
FodyWeavers.xsd
384
+
385
+
# VS Code files for those working on multiple tools
386
+
.vscode/*
387
+
!.vscode/settings.json
388
+
!.vscode/tasks.json
389
+
!.vscode/launch.json
390
+
!.vscode/extensions.json
391
+
*.code-workspace
392
+
393
+
# Local History for Visual Studio Code
394
+
.history/
395
+
396
+
# Windows Installer files from build outputs
397
+
*.cab
398
+
*.msi
399
+
*.msix
400
+
*.msm
401
+
*.msp
402
+
403
+
# JetBrains Rider
404
+
*.sln.iml
405
+
.idea/
406
+
407
+
##
408
+
## Visual studio for Mac
409
+
##
410
+
411
+
412
+
# globs
413
+
Makefile.in
414
+
*.userprefs
415
+
*.usertasks
416
+
config.make
417
+
config.status
418
+
aclocal.m4
419
+
install-sh
420
+
autom4te.cache/
421
+
*.tar.gz
422
+
tarballs/
423
+
test-results/
424
+
425
+
# Mac bundle stuff
426
+
*.dmg
427
+
*.app
428
+
429
+
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
430
+
# General
431
+
.DS_Store
432
+
.AppleDouble
433
+
.LSOverride
434
+
435
+
# Icon must end with two \r
436
+
Icon
437
+
438
+
439
+
# Thumbnails
440
+
._*
441
+
442
+
# Files that might appear in the root of a volume
443
+
.DocumentRevisions-V100
444
+
.fseventsd
445
+
.Spotlight-V100
446
+
.TemporaryItems
447
+
.Trashes
448
+
.VolumeIcon.icns
449
+
.com.apple.timemachine.donotpresent
450
+
451
+
# Directories potentially created on remote AFP share
452
+
.AppleDB
453
+
.AppleDesktop
454
+
Network Trash Folder
455
+
Temporary Items
456
+
.apdisk
457
+
458
+
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
459
+
# Windows thumbnail cache files
460
+
Thumbs.db
461
+
ehthumbs.db
462
+
ehthumbs_vista.db
463
+
464
+
# Dump file
465
+
*.stackdump
466
+
467
+
# Folder config file
468
+
[Dd]esktop.ini
469
+
470
+
# Recycle Bin used on file shares
471
+
$RECYCLE.BIN/
472
+
473
+
# Windows Installer files
474
+
*.cab
475
+
*.msi
476
+
*.msix
477
+
*.msm
478
+
*.msp
479
+
480
+
# Windows shortcuts
481
+
*.lnk
482
+
483
+
# Vim temporary swap files
484
+
*.swp
+21
AltBot.Api/AltBot.Api.csproj
+21
AltBot.Api/AltBot.Api.csproj
···
1
+
<Project Sdk="Microsoft.NET.Sdk.Web">
2
+
3
+
<PropertyGroup>
4
+
<TargetFramework>net9.0</TargetFramework>
5
+
<ImplicitUsings>enable</ImplicitUsings>
6
+
<Nullable>enable</Nullable>
7
+
</PropertyGroup>
8
+
9
+
<ItemGroup>
10
+
<ProjectReference Include="..\AltBot.Data\AltBot.Data.csproj" />
11
+
<ProjectReference Include="..\AltBot.ServiceDefaults\AltBot.ServiceDefaults.csproj" />
12
+
<ProjectReference Include="..\AltBot.Core\AltBot.Core.csproj" />
13
+
</ItemGroup>
14
+
15
+
<ItemGroup>
16
+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
17
+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
18
+
<PackageReference Include="Scalar.AspNetCore" />
19
+
</ItemGroup>
20
+
21
+
</Project>
+163
AltBot.Api/Extensions.cs
+163
AltBot.Api/Extensions.cs
···
1
+
using AltBot.Core.Models;
2
+
using AltBot.Data;
3
+
using AltBot.ServiceDefaults;
4
+
using Microsoft.EntityFrameworkCore;
5
+
6
+
namespace AltBot.Api;
7
+
8
+
public static class Extensions
9
+
{
10
+
// DTOs
11
+
record SubscriberCreate(Did Did, string? Handle);
12
+
record SubscriberUpdate(string? Handle, bool Active, string Rkey);
13
+
record PostCreate(Did Did, Cid Cid, string Rkey);
14
+
15
+
public static void MapEndpoints(this WebApplication app)
16
+
{
17
+
app.MapGet("/health", () => Results.Ok("Healthy"));
18
+
19
+
// Subscribers endpoints
20
+
app.MapGet("/subscribers", async (DataContext db) =>
21
+
{
22
+
var subscribers = await db.Subscribers
23
+
.Select(s => new
24
+
{
25
+
s.Did,
26
+
s.Handle,
27
+
s.Active,
28
+
s.Timestamp,
29
+
s.Label
30
+
})
31
+
.ToListAsync();
32
+
return Results.Ok(subscribers);
33
+
})
34
+
.WithName("GetSubscribers")
35
+
.WithOpenApi();
36
+
37
+
app.MapGet("/subscribers/{did}", async (Did did, DataContext db) =>
38
+
{
39
+
var subscriber = await db.Subscribers.FirstOrDefaultAsync(s => s.Did == did);
40
+
return subscriber == null ? Results.NotFound() : Results.Ok(subscriber);
41
+
})
42
+
.WithName("GetSubscriber")
43
+
.WithOpenApi();
44
+
45
+
app.MapGet("subscribers/{did}/posts", async (Did did, DataContext db) =>
46
+
{
47
+
var posts = await db.Posts
48
+
.Where(x => x.Did == did)
49
+
.Select(p => new
50
+
{
51
+
p.Cid,
52
+
p.Rkey,
53
+
p.Timestamp
54
+
})
55
+
.ToListAsync();
56
+
return Results.Ok(posts);
57
+
})
58
+
.WithName("GetPosts")
59
+
.WithOpenApi();
60
+
61
+
app.MapPost("/subscribers", async (SubscriberCreate request, DataContext db) =>
62
+
{
63
+
if (await db.Subscribers.AnyAsync(s => s.Did == request.Did))
64
+
{
65
+
return Results.Conflict("Subscriber already exists");
66
+
}
67
+
68
+
var subscriber = new Subscriber
69
+
{
70
+
Did = request.Did,
71
+
Handle = request.Handle,
72
+
Active = true,
73
+
Timestamp = DateTime.UtcNow
74
+
};
75
+
76
+
db.Subscribers.Add(subscriber);
77
+
await db.SaveChangesAsync();
78
+
79
+
return Results.Created($"/subscribers/{subscriber.Did}", subscriber);
80
+
})
81
+
.WithName("CreateSubscriber")
82
+
.WithOpenApi();
83
+
84
+
app.MapPut("/subscribers/{did}", async (Did did, SubscriberUpdate request, DataContext db) =>
85
+
{
86
+
var subscriber = await db.Subscribers.FindAsync(did);
87
+
if (subscriber == null)
88
+
{
89
+
return Results.NotFound();
90
+
}
91
+
92
+
subscriber.Handle = request.Handle ?? subscriber.Handle;
93
+
subscriber.Active = request.Active;
94
+
subscriber.Rkey = request.Rkey;
95
+
96
+
await db.SaveChangesAsync();
97
+
return Results.Ok(subscriber);
98
+
})
99
+
.WithName("UpdateSubscriber")
100
+
.WithOpenApi();
101
+
102
+
app.MapDelete("/subscribers/{did}", async (Did did, DataContext db) =>
103
+
{
104
+
var subscriber = await db.Subscribers.FindAsync(did);
105
+
if (subscriber == null)
106
+
{
107
+
return Results.NotFound();
108
+
}
109
+
110
+
db.Subscribers.Remove(subscriber);
111
+
await db.SaveChangesAsync();
112
+
113
+
return Results.NoContent();
114
+
})
115
+
.WithName("DeleteSubscriber")
116
+
.WithOpenApi();
117
+
118
+
119
+
// Posts endpoints
120
+
app.MapPost("/posts", async (PostCreate request, DataContext db) =>
121
+
{
122
+
if (!await db.Subscribers.AnyAsync(s => s.Did == request.Did))
123
+
{
124
+
return Results.NotFound("Subscriber not found");
125
+
}
126
+
127
+
var post = new ImagePost
128
+
{
129
+
Did = request.Did,
130
+
Cid = request.Cid,
131
+
Rkey = request.Rkey,
132
+
Timestamp = DateTime.UtcNow
133
+
};
134
+
135
+
db.Posts.Add(post);
136
+
await db.SaveChangesAsync();
137
+
138
+
return Results.Created($"/subscribers/{post.Did}/posts/{post.Cid}", post);
139
+
})
140
+
.WithName("CreatePost")
141
+
.WithOpenApi();
142
+
143
+
app.MapDelete("/posts/{did}/{cid}/{rKey}", async (Did did, Cid cid, string rKey, DataContext db) =>
144
+
{
145
+
var post = await db.Posts.FindAsync(did, cid);
146
+
if (post == null)
147
+
{
148
+
return Results.NotFound();
149
+
}
150
+
151
+
db.Posts.Remove(post);
152
+
await db.SaveChangesAsync();
153
+
154
+
return Results.NoContent();
155
+
})
156
+
.WithName("DeletePost")
157
+
.WithOpenApi();
158
+
159
+
app.MapDefaultEndpoints();
160
+
161
+
app.Run();
162
+
}
163
+
}
+52
AltBot.Api/Program.cs
+52
AltBot.Api/Program.cs
···
1
+
using AltBot.Api;
2
+
using AltBot.Data;
3
+
using AltBot.ServiceDefaults;
4
+
using Microsoft.EntityFrameworkCore;
5
+
using Scalar.AspNetCore;
6
+
7
+
var builder = WebApplication.CreateBuilder(args);
8
+
9
+
// Add service defaults & Aspire client integrations.
10
+
builder.AddServiceDefaults();
11
+
12
+
// Add services to the container.
13
+
builder.Services.AddProblemDetails();
14
+
15
+
// Add DbContext
16
+
builder.Services.AddDbContext<DataContext>(options =>
17
+
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
18
+
19
+
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
20
+
builder.Services.AddOpenApi();
21
+
22
+
var app = builder.Build();
23
+
24
+
// Configure the HTTP request pipeline.
25
+
app.UseExceptionHandler();
26
+
27
+
if (app.Environment.IsDevelopment())
28
+
{
29
+
app.MapOpenApi();
30
+
app.MapScalarApiReference(options =>
31
+
{
32
+
List<ScalarServer> servers = [];
33
+
34
+
string? httpsPort = Environment.GetEnvironmentVariable("ASPNETCORE_HTTPS_PORT");
35
+
if (httpsPort is not null)
36
+
{
37
+
servers.Add(new ScalarServer($"https://localhost:{httpsPort}"));
38
+
}
39
+
40
+
string? httpPort = Environment.GetEnvironmentVariable("ASPNETCORE_HTTP_PORT");
41
+
if (httpPort is not null)
42
+
{
43
+
servers.Add(new ScalarServer($"http://localhost:{httpPort}"));
44
+
}
45
+
46
+
options.Servers = servers;
47
+
options.Title = "AltHeroes Bot Management API";
48
+
options.ShowSidebar = true;
49
+
});
50
+
}
51
+
52
+
app.MapEndpoints();
+23
AltBot.Api/Properties/launchSettings.json
+23
AltBot.Api/Properties/launchSettings.json
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/launchsettings.json",
3
+
"profiles": {
4
+
"http": {
5
+
"commandName": "Project",
6
+
"dotnetRunMessages": true,
7
+
"launchBrowser": false,
8
+
"applicationUrl": "http://localhost:5535",
9
+
"environmentVariables": {
10
+
"ASPNETCORE_ENVIRONMENT": "Development"
11
+
}
12
+
},
13
+
"https": {
14
+
"commandName": "Project",
15
+
"dotnetRunMessages": true,
16
+
"launchBrowser": false,
17
+
"applicationUrl": "https://localhost:7478;http://localhost:5535",
18
+
"environmentVariables": {
19
+
"ASPNETCORE_ENVIRONMENT": "Development"
20
+
}
21
+
}
22
+
}
23
+
}
+8
AltBot.Api/appsettings.Development.json
+8
AltBot.Api/appsettings.Development.json
+12
AltBot.Api/appsettings.json
+12
AltBot.Api/appsettings.json
···
1
+
{
2
+
"Logging": {
3
+
"LogLevel": {
4
+
"Default": "Information",
5
+
"Microsoft.AspNetCore": "Warning"
6
+
}
7
+
},
8
+
"AllowedHosts": "*",
9
+
"ConnectionStrings": {
10
+
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=LabelerBot;Trusted_Connection=True;MultipleActiveResultSets=true"
11
+
}
12
+
}
+22
AltBot.AppHost/AltBot.AppHost.csproj
+22
AltBot.AppHost/AltBot.AppHost.csproj
···
1
+
<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+
<Sdk Name="Aspire.AppHost.Sdk" Version="9.3.1" />
4
+
5
+
<PropertyGroup>
6
+
<OutputType>Exe</OutputType>
7
+
<TargetFramework>net9.0</TargetFramework>
8
+
<ImplicitUsings>enable</ImplicitUsings>
9
+
<Nullable>enable</Nullable>
10
+
<UserSecretsId>c08ca60f-6055-49fe-bb10-e2b939dbdf4f</UserSecretsId>
11
+
</PropertyGroup>
12
+
13
+
<ItemGroup>
14
+
<ProjectReference Include="..\AltBot.Api\AltBot.Api.csproj" />
15
+
<ProjectReference Include="..\AltBot.Worker\AltBot.Worker.csproj" />
16
+
</ItemGroup>
17
+
18
+
<ItemGroup>
19
+
<PackageReference Include="Aspire.Hosting.AppHost" />
20
+
</ItemGroup>
21
+
22
+
</Project>
+8
AltBot.AppHost/AppHost.cs
+8
AltBot.AppHost/AppHost.cs
+29
AltBot.AppHost/Properties/launchSettings.json
+29
AltBot.AppHost/Properties/launchSettings.json
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/launchsettings.json",
3
+
"profiles": {
4
+
"https": {
5
+
"commandName": "Project",
6
+
"dotnetRunMessages": true,
7
+
"launchBrowser": true,
8
+
"applicationUrl": "https://localhost:17034;http://localhost:15182",
9
+
"environmentVariables": {
10
+
"ASPNETCORE_ENVIRONMENT": "Development",
11
+
"DOTNET_ENVIRONMENT": "Development",
12
+
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21244",
13
+
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22128"
14
+
}
15
+
},
16
+
"http": {
17
+
"commandName": "Project",
18
+
"dotnetRunMessages": true,
19
+
"launchBrowser": true,
20
+
"applicationUrl": "http://localhost:15182",
21
+
"environmentVariables": {
22
+
"ASPNETCORE_ENVIRONMENT": "Development",
23
+
"DOTNET_ENVIRONMENT": "Development",
24
+
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19021",
25
+
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20070"
26
+
}
27
+
}
28
+
}
29
+
}
+8
AltBot.AppHost/appsettings.Development.json
+8
AltBot.AppHost/appsettings.Development.json
+9
AltBot.AppHost/appsettings.json
+9
AltBot.AppHost/appsettings.json
+14
AltBot.Core/AltBot.Core.csproj
+14
AltBot.Core/AltBot.Core.csproj
···
1
+
<?xml version="1.0" encoding="utf-8"?>
2
+
<Project Sdk="Microsoft.NET.Sdk">
3
+
4
+
<PropertyGroup>
5
+
<TargetFramework>net9.0</TargetFramework>
6
+
<ImplicitUsings>enable</ImplicitUsings>
7
+
<Nullable>enable</Nullable>
8
+
</PropertyGroup>
9
+
10
+
<ItemGroup>
11
+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
12
+
</ItemGroup>
13
+
14
+
</Project>
+116
AltBot.Core/Models/Cid.cs
+116
AltBot.Core/Models/Cid.cs
···
1
+
using Microsoft.Extensions.Logging;
2
+
using System.Diagnostics.CodeAnalysis;
3
+
using System.Text.RegularExpressions;
4
+
5
+
namespace AltBot.Core.Models;
6
+
7
+
/// <summary>
8
+
/// Represents a Content Identifier (CID)
9
+
/// </summary>
10
+
public class Cid : IParsable<Cid>
11
+
{
12
+
//private static readonly ILogger<Cid>? _logger;
13
+
private const int MaxLength = 1024;
14
+
15
+
// Internal constructor for EF Core
16
+
internal Cid(string value)
17
+
{
18
+
Value = value;
19
+
}
20
+
21
+
public string Value { get; }
22
+
23
+
/// <summary>
24
+
/// Creates a new Cid instance after validating the input string
25
+
/// </summary>
26
+
/// <param name="rawCid">The raw CID string to validate</param>
27
+
/// <returns>A new Cid instance if valid, null otherwise</returns>
28
+
public static Cid? Create(string rawCid)
29
+
{
30
+
return ValidateCid(rawCid) ? new Cid(rawCid) : null;
31
+
}
32
+
33
+
// For use when the Cid is known to be valid (e.g., loading from database)
34
+
public static Cid Load(string rawCid)
35
+
{
36
+
return new Cid(rawCid);
37
+
}
38
+
39
+
private static bool ValidateCid(string cid)
40
+
{
41
+
if (string.IsNullOrWhiteSpace(cid))
42
+
{
43
+
//_logger?.LogError("CID cannot be null or empty");
44
+
return false;
45
+
}
46
+
47
+
if (cid.Length > MaxLength)
48
+
{
49
+
//_logger?.LogError("CID is too long ({MaxLength} chars max)", MaxLength);
50
+
return false;
51
+
}
52
+
53
+
// Basic CID validation - should be base32 or base58 encoded
54
+
if (!Regex.IsMatch(cid, "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$"))
55
+
{
56
+
//_logger?.LogError("CID contains invalid characters");
57
+
return false;
58
+
}
59
+
60
+
return true;
61
+
}
62
+
63
+
public static Cid Parse(string s, IFormatProvider? provider)
64
+
{
65
+
if (TryParse(s, provider, out var result))
66
+
{
67
+
return result;
68
+
}
69
+
throw new FormatException($"Unable to parse CID: {s}");
70
+
}
71
+
72
+
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Cid result)
73
+
{
74
+
if (s != null && ValidateCid(s))
75
+
{
76
+
result = new Cid(s);
77
+
return true;
78
+
}
79
+
result = null;
80
+
return false;
81
+
}
82
+
83
+
public override string ToString() => Value;
84
+
85
+
public override bool Equals(object? obj)
86
+
{
87
+
if (obj is Cid other)
88
+
{
89
+
return Value.Equals(other.Value, StringComparison.Ordinal);
90
+
}
91
+
return false;
92
+
}
93
+
94
+
public override int GetHashCode() => Value.GetHashCode(StringComparison.Ordinal);
95
+
96
+
public static implicit operator string(Cid cid) => cid.Value;
97
+
98
+
public static explicit operator Cid(string s) => Parse(s, null);
99
+
100
+
public static bool operator ==(Cid? left, Cid? right)
101
+
{
102
+
if (ReferenceEquals(left, right))
103
+
{
104
+
return true;
105
+
}
106
+
107
+
if (left is null || right is null)
108
+
{
109
+
return false;
110
+
}
111
+
112
+
return left.Equals(right);
113
+
}
114
+
115
+
public static bool operator !=(Cid? left, Cid? right) => !(left == right);
116
+
}
+115
AltBot.Core/Models/Did.cs
+115
AltBot.Core/Models/Did.cs
···
1
+
using System.Diagnostics.CodeAnalysis;
2
+
using System.Text.RegularExpressions;
3
+
using Microsoft.Extensions.Logging;
4
+
5
+
namespace AltBot.Core.Models;
6
+
7
+
/// <summary>
8
+
/// Represents a Decentralized Identifier (DID)
9
+
/// </summary>
10
+
public class Did : IParsable<Did>
11
+
{
12
+
//private static readonly ILogger<Did>? _logger;
13
+
private const int MaxLength = 2048;
14
+
15
+
// Internal constructor for EF Core
16
+
internal Did(string value)
17
+
{
18
+
Value = value;
19
+
}
20
+
21
+
public string Value { get; }
22
+
23
+
/// <summary>
24
+
/// Creates a new Did instance after validating the input string
25
+
/// </summary>
26
+
/// <param name="rawDid">The raw DID string to validate</param>
27
+
/// <returns>A new Did instance if valid, null otherwise</returns>
28
+
public static Did? Create(string rawDid)
29
+
{
30
+
return ValidateDid(rawDid) ? new Did(rawDid) : null;
31
+
}
32
+
33
+
// For use when the Did is known to be valid (e.g., loading from database)
34
+
public static Did Load(string rawDid)
35
+
{
36
+
return new Did(rawDid);
37
+
}
38
+
39
+
private static bool ValidateDid(string did)
40
+
{
41
+
if (string.IsNullOrWhiteSpace(did))
42
+
{
43
+
//_logger?.LogError("DID cannot be null or empty");
44
+
return false;
45
+
}
46
+
47
+
if (did.Split(':').Length < 3)
48
+
{
49
+
//_logger?.LogError("DID requires prefix, method, and method-specific content");
50
+
return false;
51
+
}
52
+
53
+
if (did.Length > MaxLength)
54
+
{
55
+
//_logger?.LogError("DID is too long ({MaxLength} chars max)", MaxLength);
56
+
return false;
57
+
}
58
+
59
+
return true;
60
+
}
61
+
62
+
public static Did Parse(string s, IFormatProvider? provider)
63
+
{
64
+
if (TryParse(s, provider, out var result))
65
+
{
66
+
return result;
67
+
}
68
+
throw new FormatException($"Unable to parse DID: {s}");
69
+
}
70
+
71
+
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Did result)
72
+
{
73
+
if (s != null && ValidateDid(s))
74
+
{
75
+
result = new Did(s);
76
+
return true;
77
+
}
78
+
result = null;
79
+
return false;
80
+
}
81
+
82
+
public override string ToString() => Value;
83
+
84
+
public override bool Equals(object? obj)
85
+
{
86
+
if (obj is Did other)
87
+
{
88
+
return Value.Equals(other.Value, StringComparison.Ordinal);
89
+
}
90
+
return false;
91
+
}
92
+
93
+
public override int GetHashCode() => Value.GetHashCode(StringComparison.Ordinal);
94
+
95
+
public static implicit operator string(Did did) => did.Value;
96
+
97
+
public static explicit operator Did(string s) => Parse(s, null);
98
+
99
+
public static bool operator ==(Did? left, Did? right)
100
+
{
101
+
if (ReferenceEquals(left, right))
102
+
{
103
+
return true;
104
+
}
105
+
106
+
if (left is null || right is null)
107
+
{
108
+
return false;
109
+
}
110
+
111
+
return left.Equals(right);
112
+
}
113
+
114
+
public static bool operator !=(Did? left, Did? right) => !(left == right);
115
+
}
+12
AltBot.Core/Models/ImagePost.cs
+12
AltBot.Core/Models/ImagePost.cs
···
1
+
2
+
namespace AltBot.Core.Models;
3
+
4
+
public class ImagePost
5
+
{
6
+
public required Did Did { get; set; }
7
+
public required Cid Cid { get; set; }
8
+
public string? Rkey { get; set; }
9
+
public bool ValidAlt { get; set; }
10
+
public DateTime Timestamp { get; set; }
11
+
public virtual Subscriber? Subscriber { get; set; }
12
+
}
+10
AltBot.Core/Models/LabelLevel.cs
+10
AltBot.Core/Models/LabelLevel.cs
+10
AltBot.Core/Models/LabelLevelEnum.cs
+10
AltBot.Core/Models/LabelLevelEnum.cs
+13
AltBot.Core/Models/Subscriber.cs
+13
AltBot.Core/Models/Subscriber.cs
···
1
+
2
+
namespace AltBot.Core.Models;
3
+
4
+
public class Subscriber
5
+
{
6
+
public required Did Did { get; set; }
7
+
public DateTime Timestamp { get; set; }
8
+
public bool Active { get; set; }
9
+
public string? Handle { get; set; }
10
+
public string? Rkey { get; set; }
11
+
public virtual ICollection<ImagePost> Posts { get; set; } = new List<ImagePost>();
12
+
public virtual LabelLevel Label { get; set; }
13
+
}
+29
AltBot.Data/AltBot.Data.csproj
+29
AltBot.Data/AltBot.Data.csproj
···
1
+
<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+
<PropertyGroup>
4
+
<TargetFramework>net9.0</TargetFramework>
5
+
<ImplicitUsings>enable</ImplicitUsings>
6
+
<Nullable>enable</Nullable>
7
+
</PropertyGroup>
8
+
9
+
<ItemGroup>
10
+
<Compile Remove="sql\**" />
11
+
<EmbeddedResource Remove="sql\**" />
12
+
<None Remove="sql\**" />
13
+
</ItemGroup>
14
+
15
+
<ItemGroup>
16
+
<PackageReference Include="Microsoft.EntityFrameworkCore" />
17
+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
18
+
<PrivateAssets>all</PrivateAssets>
19
+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20
+
</PackageReference>
21
+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
22
+
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
23
+
</ItemGroup>
24
+
25
+
<ItemGroup>
26
+
<ProjectReference Include="..\AltBot.Core\AltBot.Core.csproj" />
27
+
</ItemGroup>
28
+
29
+
</Project>
+81
AltBot.Data/DataContext.cs
+81
AltBot.Data/DataContext.cs
···
1
+
using AltBot.Core.Models;
2
+
using Microsoft.EntityFrameworkCore;
3
+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
4
+
5
+
namespace AltBot.Data;
6
+
7
+
public class DataContext(DbContextOptions<DataContext> options) : DbContext(options)
8
+
{
9
+
public DbSet<ImagePost> Posts { get; set; }
10
+
public DbSet<Subscriber> Subscribers { get; set; }
11
+
12
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
13
+
{
14
+
base.OnModelCreating(modelBuilder);
15
+
16
+
var didConverter = new ValueConverter<Did, string>(
17
+
v => v.Value,
18
+
v => Did.Load(v));
19
+
20
+
var labelConverter = new ValueConverter<LabelLevel, string>(
21
+
v => v.ToString(),
22
+
v => Enum.Parse<LabelLevel>(v));
23
+
24
+
modelBuilder.Entity<ImagePost>(entity =>
25
+
{
26
+
entity.ToTable("imagePost");
27
+
28
+
entity.HasKey(x => new { x.Did, x.Cid });
29
+
30
+
entity.Property(x => x.Did)
31
+
.HasColumnName("did")
32
+
.HasMaxLength(2048)
33
+
.HasConversion(didConverter);
34
+
35
+
entity.Property(x => x.Cid)
36
+
.HasColumnName("cid")
37
+
.HasMaxLength(1000)
38
+
.HasConversion(
39
+
v => v.ToString(),
40
+
v => Cid.Load(v));
41
+
42
+
entity.Property(x => x.Rkey)
43
+
.HasColumnName("rKey")
44
+
.HasMaxLength(512);
45
+
46
+
entity.HasOne(x => x.Subscriber)
47
+
.WithMany(x => x.Posts)
48
+
.HasForeignKey(x => x.Did)
49
+
.HasConstraintName("FK_ImagePost_Subscriber");
50
+
});
51
+
52
+
modelBuilder.Entity<Subscriber>(entity =>
53
+
{
54
+
entity.ToTable("subscriber");
55
+
56
+
entity.HasKey(x => x.Did);
57
+
58
+
entity.Property(x => x.Did)
59
+
.HasColumnName("did")
60
+
.HasMaxLength(2048)
61
+
.HasConversion(didConverter);
62
+
63
+
entity.Property(x => x.Active)
64
+
.HasDefaultValue(true);
65
+
66
+
entity.Property(x => x.Handle)
67
+
.IsUnicode()
68
+
.HasMaxLength(250);
69
+
70
+
entity.Property(x => x.Label)
71
+
.HasColumnName("label")
72
+
.HasConversion(labelConverter)
73
+
.HasDefaultValue(LabelLevel.None);
74
+
75
+
76
+
entity.Property(x => x.Rkey)
77
+
.HasColumnName("RKey")
78
+
.HasMaxLength(100);
79
+
});
80
+
}
81
+
}
+28
AltBot.ServiceDefaults/AltBot.ServiceDefaults.csproj
+28
AltBot.ServiceDefaults/AltBot.ServiceDefaults.csproj
···
1
+
<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+
<PropertyGroup>
4
+
<TargetFramework>net9.0</TargetFramework>
5
+
<ImplicitUsings>enable</ImplicitUsings>
6
+
<Nullable>enable</Nullable>
7
+
<IsAspireSharedProject>true</IsAspireSharedProject>
8
+
</PropertyGroup>
9
+
10
+
<ItemGroup>
11
+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
12
+
13
+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
14
+
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
15
+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
16
+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
17
+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
18
+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
19
+
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
20
+
<PackageReference Include="Serilog.AspNetCore" />
21
+
<PackageReference Include="Serilog.Enrichers.Environment" />
22
+
<PackageReference Include="Serilog.Enrichers.Thread" />
23
+
<PackageReference Include="Serilog.Settings.Configuration" />
24
+
<PackageReference Include="Serilog.Sinks.Console" />
25
+
<PackageReference Include="Serilog.Sinks.Debug" />
26
+
</ItemGroup>
27
+
28
+
</Project>
+151
AltBot.ServiceDefaults/Extensions.cs
+151
AltBot.ServiceDefaults/Extensions.cs
···
1
+
using Microsoft.AspNetCore.Builder;
2
+
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
3
+
using Microsoft.Extensions.DependencyInjection;
4
+
using Microsoft.Extensions.Diagnostics.HealthChecks;
5
+
using Microsoft.Extensions.Hosting;
6
+
using Microsoft.Extensions.Logging;
7
+
using Microsoft.Extensions.ServiceDiscovery;
8
+
using OpenTelemetry;
9
+
using OpenTelemetry.Metrics;
10
+
using OpenTelemetry.Trace;
11
+
using Serilog;
12
+
using Serilog.Events;
13
+
14
+
namespace AltBot.ServiceDefaults;
15
+
16
+
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
17
+
// This project should be referenced by each service project in your solution.
18
+
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
19
+
public static class Extensions
20
+
{
21
+
private const string HealthEndpointPath = "/health";
22
+
private const string AlivenessEndpointPath = "/alive";
23
+
24
+
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
25
+
{
26
+
builder.ConfigureSerilog();
27
+
28
+
builder.ConfigureOpenTelemetry();
29
+
30
+
builder.AddDefaultHealthChecks();
31
+
32
+
builder.Services.AddServiceDiscovery();
33
+
34
+
builder.Services.ConfigureHttpClientDefaults(http =>
35
+
{
36
+
// Turn on resilience by default
37
+
http.AddStandardResilienceHandler();
38
+
39
+
// Turn on service discovery by default
40
+
http.AddServiceDiscovery();
41
+
});
42
+
43
+
return builder;
44
+
}
45
+
46
+
private static TBuilder ConfigureSerilog<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
47
+
{
48
+
// Create Serilog logger configuration
49
+
var loggerConfiguration = new LoggerConfiguration()
50
+
.ReadFrom.Configuration(builder.Configuration)
51
+
.MinimumLevel.Information()
52
+
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
53
+
.MinimumLevel.Override("System", LogEventLevel.Warning)
54
+
.Enrich.FromLogContext()
55
+
.Enrich.WithEnvironmentName()
56
+
.Enrich.WithThreadId();
57
+
58
+
// Add console sink in development
59
+
if (builder.Environment.IsDevelopment())
60
+
{
61
+
loggerConfiguration
62
+
.WriteTo.Console()
63
+
.WriteTo.Debug();
64
+
}
65
+
else
66
+
{
67
+
loggerConfiguration
68
+
.WriteTo.Console();
69
+
}
70
+
71
+
// Create logger
72
+
Log.Logger = loggerConfiguration.CreateLogger();
73
+
74
+
// Configure application to use Serilog
75
+
builder.Logging.AddSerilog(dispose: true);
76
+
77
+
return builder;
78
+
}
79
+
80
+
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
81
+
{
82
+
builder.Logging.AddOpenTelemetry(logging =>
83
+
{
84
+
logging.IncludeFormattedMessage = true;
85
+
logging.IncludeScopes = true;
86
+
});
87
+
88
+
builder.Services.AddOpenTelemetry()
89
+
.WithMetrics(metrics =>
90
+
{
91
+
metrics.AddAspNetCoreInstrumentation()
92
+
.AddHttpClientInstrumentation()
93
+
.AddRuntimeInstrumentation();
94
+
})
95
+
.WithTracing(tracing =>
96
+
{
97
+
tracing.AddSource(builder.Environment.ApplicationName)
98
+
.AddAspNetCoreInstrumentation(tracing =>
99
+
// Exclude health check requests from tracing
100
+
tracing.Filter = context =>
101
+
!context.Request.Path.StartsWithSegments(HealthEndpointPath)
102
+
&& !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)
103
+
)
104
+
.AddHttpClientInstrumentation();
105
+
});
106
+
107
+
builder.AddOpenTelemetryExporters();
108
+
109
+
return builder;
110
+
}
111
+
112
+
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
113
+
{
114
+
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
115
+
116
+
if (useOtlpExporter)
117
+
{
118
+
builder.Services.AddOpenTelemetry().UseOtlpExporter();
119
+
}
120
+
121
+
return builder;
122
+
}
123
+
124
+
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
125
+
{
126
+
builder.Services.AddHealthChecks()
127
+
// Add a default liveness check to ensure app is responsive
128
+
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
129
+
130
+
return builder;
131
+
}
132
+
133
+
public static WebApplication MapDefaultEndpoints(this WebApplication app)
134
+
{
135
+
// Adding health checks endpoints to applications in non-development environments has security implications.
136
+
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
137
+
if (app.Environment.IsDevelopment())
138
+
{
139
+
// All health checks must pass for app to be considered ready to accept traffic after starting
140
+
app.MapHealthChecks(HealthEndpointPath);
141
+
142
+
// Only health checks tagged with the "live" tag must pass for app to be considered alive
143
+
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
144
+
{
145
+
Predicate = r => r.Tags.Contains("live")
146
+
});
147
+
}
148
+
149
+
return app;
150
+
}
151
+
}
+19
AltBot.Worker/AltBot.Worker.csproj
+19
AltBot.Worker/AltBot.Worker.csproj
···
1
+
<Project Sdk="Microsoft.NET.Sdk.Worker">
2
+
3
+
<PropertyGroup>
4
+
<TargetFramework>net9.0</TargetFramework>
5
+
<Nullable>enable</Nullable>
6
+
<ImplicitUsings>enable</ImplicitUsings>
7
+
<UserSecretsId>dotnet-AltBot.Worker-b4ad104b-8a9e-44fa-a4dd-9d7d868d5a35</UserSecretsId>
8
+
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
9
+
</PropertyGroup>
10
+
11
+
<ItemGroup>
12
+
<PackageReference Include="idunno.AtProto" />
13
+
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
14
+
</ItemGroup>
15
+
16
+
<ItemGroup>
17
+
<ProjectReference Include="..\AltBot.ServiceDefaults\AltBot.ServiceDefaults.csproj" />
18
+
</ItemGroup>
19
+
</Project>
+29
AltBot.Worker/Dockerfile
+29
AltBot.Worker/Dockerfile
···
1
+
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
2
+
3
+
# This stage is used when running from VS in fast mode (Default for Debug configuration)
4
+
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
5
+
USER $APP_UID
6
+
WORKDIR /app
7
+
8
+
9
+
# This stage is used to build the service project
10
+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
11
+
ARG BUILD_CONFIGURATION=Release
12
+
WORKDIR /src
13
+
COPY ["Directory.Packages.props", "."]
14
+
COPY ["AltBot.Worker/AltBot.Worker.csproj", "AltBot.Worker/"]
15
+
RUN dotnet restore "./AltBot.Worker/AltBot.Worker.csproj"
16
+
COPY . .
17
+
WORKDIR "/src/AltBot.Worker"
18
+
RUN dotnet build "./AltBot.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/build
19
+
20
+
# This stage is used to publish the service project to be copied to the final stage
21
+
FROM build AS publish
22
+
ARG BUILD_CONFIGURATION=Release
23
+
RUN dotnet publish "./AltBot.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
24
+
25
+
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
26
+
FROM base AS final
27
+
WORKDIR /app
28
+
COPY --from=publish /app/publish .
29
+
ENTRYPOINT ["dotnet", "AltBot.Worker.dll"]
+10
AltBot.Worker/Program.cs
+10
AltBot.Worker/Program.cs
+15
AltBot.Worker/Properties/launchSettings.json
+15
AltBot.Worker/Properties/launchSettings.json
···
1
+
{
2
+
"profiles": {
3
+
"AltBot.Worker": {
4
+
"commandName": "Project",
5
+
"environmentVariables": {
6
+
"DOTNET_ENVIRONMENT": "Development"
7
+
},
8
+
"dotnetRunMessages": true
9
+
},
10
+
"Container (Dockerfile)": {
11
+
"commandName": "Docker"
12
+
}
13
+
},
14
+
"$schema": "https://json.schemastore.org/launchsettings.json"
15
+
}
+23
AltBot.Worker/Worker.cs
+23
AltBot.Worker/Worker.cs
···
1
+
namespace AltBot.Worker;
2
+
3
+
public class Worker : BackgroundService
4
+
{
5
+
private readonly ILogger<Worker> _logger;
6
+
7
+
public Worker(ILogger<Worker> logger)
8
+
{
9
+
_logger = logger;
10
+
}
11
+
12
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
13
+
{
14
+
while (!stoppingToken.IsCancellationRequested)
15
+
{
16
+
if (_logger.IsEnabled(LogLevel.Information))
17
+
{
18
+
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
19
+
}
20
+
await Task.Delay(1000, stoppingToken);
21
+
}
22
+
}
23
+
}
+8
AltBot.Worker/appsettings.Development.json
+8
AltBot.Worker/appsettings.Development.json
+8
AltBot.Worker/appsettings.json
+8
AltBot.Worker/appsettings.json
+54
AltBot.sln
+54
AltBot.sln
···
1
+
Microsoft Visual Studio Solution File, Format Version 12.00
2
+
# Visual Studio Version 18
3
+
VisualStudioVersion = 18.0.11018.127
4
+
MinimumVisualStudioVersion = 10.0.40219.1
5
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.AppHost", "AltBot.AppHost\AltBot.AppHost.csproj", "{07FAEC9B-E11E-46CD-A30F-55D47BB65CE8}"
6
+
EndProject
7
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.ServiceDefaults", "AltBot.ServiceDefaults\AltBot.ServiceDefaults.csproj", "{F928DEAE-0ED9-E7E7-AFAA-F66602E366D8}"
8
+
EndProject
9
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.Api", "AltBot.Api\AltBot.Api.csproj", "{B6BBCF2D-C652-3B38-246B-F4AC6ED07B24}"
10
+
EndProject
11
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.Data", "AltBot.Data\AltBot.Data.csproj", "{59D55BE0-CE8D-C025-E265-02ACCDD1F49E}"
12
+
EndProject
13
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.Core", "AltBot.Core\AltBot.Core.csproj", "{393C2DD8-A5E5-5F63-56D7-0E3890AB6E17}"
14
+
EndProject
15
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltBot.Worker", "AltBot.Worker\AltBot.Worker.csproj", "{B0853F52-805C-69D3-53B7-5E7EF2EA6C8F}"
16
+
EndProject
17
+
Global
18
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
19
+
Debug|Any CPU = Debug|Any CPU
20
+
Release|Any CPU = Release|Any CPU
21
+
EndGlobalSection
22
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
23
+
{07FAEC9B-E11E-46CD-A30F-55D47BB65CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24
+
{07FAEC9B-E11E-46CD-A30F-55D47BB65CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
25
+
{07FAEC9B-E11E-46CD-A30F-55D47BB65CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
26
+
{07FAEC9B-E11E-46CD-A30F-55D47BB65CE8}.Release|Any CPU.Build.0 = Release|Any CPU
27
+
{F928DEAE-0ED9-E7E7-AFAA-F66602E366D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28
+
{F928DEAE-0ED9-E7E7-AFAA-F66602E366D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
29
+
{F928DEAE-0ED9-E7E7-AFAA-F66602E366D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
30
+
{F928DEAE-0ED9-E7E7-AFAA-F66602E366D8}.Release|Any CPU.Build.0 = Release|Any CPU
31
+
{B6BBCF2D-C652-3B38-246B-F4AC6ED07B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32
+
{B6BBCF2D-C652-3B38-246B-F4AC6ED07B24}.Debug|Any CPU.Build.0 = Debug|Any CPU
33
+
{B6BBCF2D-C652-3B38-246B-F4AC6ED07B24}.Release|Any CPU.ActiveCfg = Release|Any CPU
34
+
{B6BBCF2D-C652-3B38-246B-F4AC6ED07B24}.Release|Any CPU.Build.0 = Release|Any CPU
35
+
{59D55BE0-CE8D-C025-E265-02ACCDD1F49E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36
+
{59D55BE0-CE8D-C025-E265-02ACCDD1F49E}.Debug|Any CPU.Build.0 = Debug|Any CPU
37
+
{59D55BE0-CE8D-C025-E265-02ACCDD1F49E}.Release|Any CPU.ActiveCfg = Release|Any CPU
38
+
{59D55BE0-CE8D-C025-E265-02ACCDD1F49E}.Release|Any CPU.Build.0 = Release|Any CPU
39
+
{393C2DD8-A5E5-5F63-56D7-0E3890AB6E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40
+
{393C2DD8-A5E5-5F63-56D7-0E3890AB6E17}.Debug|Any CPU.Build.0 = Debug|Any CPU
41
+
{393C2DD8-A5E5-5F63-56D7-0E3890AB6E17}.Release|Any CPU.ActiveCfg = Release|Any CPU
42
+
{393C2DD8-A5E5-5F63-56D7-0E3890AB6E17}.Release|Any CPU.Build.0 = Release|Any CPU
43
+
{B0853F52-805C-69D3-53B7-5E7EF2EA6C8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44
+
{B0853F52-805C-69D3-53B7-5E7EF2EA6C8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
45
+
{B0853F52-805C-69D3-53B7-5E7EF2EA6C8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
46
+
{B0853F52-805C-69D3-53B7-5E7EF2EA6C8F}.Release|Any CPU.Build.0 = Release|Any CPU
47
+
EndGlobalSection
48
+
GlobalSection(SolutionProperties) = preSolution
49
+
HideSolutionNode = FALSE
50
+
EndGlobalSection
51
+
GlobalSection(ExtensibilityGlobals) = postSolution
52
+
SolutionGuid = {91CE63A7-19CC-43C5-AD9D-B1EBC32F805A}
53
+
EndGlobalSection
54
+
EndGlobal
+40
Directory.Packages.props
+40
Directory.Packages.props
···
1
+
<Project>
2
+
<PropertyGroup>
3
+
<!-- Enable central package management, https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
4
+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
5
+
</PropertyGroup>
6
+
<ItemGroup>
7
+
<!-- Aspire -->
8
+
<PackageVersion Include="Aspire.Hosting.AppHost" Version="9.5.1" />
9
+
<PackageVersion Include="Aspire.Hosting.Testing" Version="9.5.1" />
10
+
<!-- Entity Framework -->
11
+
<PackageVersion Include="idunno.AtProto" Version="1.1.0" />
12
+
<PackageVersion Include="idunno.Bluesky" Version="1.1.0" />
13
+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
14
+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9" />
15
+
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.9" />
16
+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
17
+
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
18
+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
19
+
<!-- ASP.NET Core -->
20
+
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
21
+
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.9.0" />
22
+
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.5.1" />
23
+
<!-- Testing -->
24
+
<PackageVersion Include="MSTest" Version="3.11.0" />
25
+
<!-- OpenTelemetry -->
26
+
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.13.0" />
27
+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.13.0" />
28
+
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
29
+
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
30
+
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
31
+
<!-- Serilog -->
32
+
<PackageVersion Include="Scalar.AspNetCore" Version="2.8.10" />
33
+
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
34
+
<PackageVersion Include="Serilog.Enrichers.Environment" Version="3.0.1" />
35
+
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
36
+
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
37
+
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
38
+
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
39
+
</ItemGroup>
40
+
</Project>