summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2026-01-30 15:36:04 +0100
committerRory& <root@rory.gay>2026-01-30 15:36:04 +0100
commit43641ed0398bb772f2f2768234595a069ec14141 (patch)
treef62ce870436544bd301c3f7034bc2f7da079e96c
parentcs cdn interop stuff (diff)
downloadserver-ts-43641ed0398bb772f2f2768234595a069ec14141.tar.xz
cdn-cs updates
-rw-r--r--extra/admin-api/Spacebar.Cdn/Controllers/GetImageController.cs8
-rw-r--r--extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs5
-rw-r--r--extra/admin-api/SpacebarAdminAPI.sln30
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Fsck/Program.cs4
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/FsckService.cs210
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/MigrationFileStores.cs8
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/Program.cs57
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/Properties/launchSettings.json19
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/Spacebar.Cdn.Migration.csproj20
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.Development.json8
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.json8
-rw-r--r--extra/admin-api/Utilities/Spacebar.Cdn.Migration/deps.json332
12 files changed, 706 insertions, 3 deletions
diff --git a/extra/admin-api/Spacebar.Cdn/Controllers/GetImageController.cs b/extra/admin-api/Spacebar.Cdn/Controllers/GetImageController.cs

index 2f014926..9a7ddf23 100644 --- a/extra/admin-api/Spacebar.Cdn/Controllers/GetImageController.cs +++ b/extra/admin-api/Spacebar.Cdn/Controllers/GetImageController.cs
@@ -53,4 +53,12 @@ public class GetImageController(LruFileCache lfc, IFileSource fs, DiscordImageRe // byte array with mime type result return new FileContentResult(entry.Data, entry.MimeType); } + + // TODO: is message_id required? Can't tell from discord.food: /attachments/{channel_id}/[{message_id]/attachment_id}/{attachment_filename} + [HttpGet("/attachments/{channel_id:required}/{message_id:required}/{attachment_id:required}/{filename:required}")] + [HttpGet("/ephemeral-attachments/{application_id:required}/{attachment_id:required}/{attachment_filename:required}")] + public async Task<IActionResult> GetAttachmentImage() { + // TODO: url signing, file type checks + return await GetImage(""); + } } \ No newline at end of file diff --git a/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs b/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs
index dd8d4255..404e6e29 100644 --- a/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs +++ b/extra/admin-api/Spacebar.Cdn/Controllers/Internal/GetImageController.cs
@@ -6,7 +6,7 @@ using Spacebar.Cdn.Extensions; using Spacebar.Interop.Cdn.Abstractions; namespace Spacebar.Cdn.Controllers.Internal; - +/* [ApiController] public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDetectionService pads, DiscordImageResizeService dirs) : ControllerBase { private static readonly LruCache<bool> _isPixelArtCache = new(100_000); @@ -145,4 +145,5 @@ public class IsPixelArtController(LruFileCache lfc, IFileSource fs, PixelArtDete }; }).ContinueWith(t => File(t.Result.Data, t.Result.MimeType)); } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/extra/admin-api/SpacebarAdminAPI.sln b/extra/admin-api/SpacebarAdminAPI.sln
index 3235d6d2..2d76cb82 100644 --- a/extra/admin-api/SpacebarAdminAPI.sln +++ b/extra/admin-api/SpacebarAdminAPI.sln
@@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spacebar.Cdn.Fsck", "Utilit EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spacebar.Interop.Cdn.Abstractions", "Interop\Spacebar.Interop.Cdn.Abstractions\Spacebar.Interop.Cdn.Abstractions.csproj", "{B494C58D-2D53-49F4-9E3E-1DCF60828802}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spacebar.Cdn.Migration", "Utilities\Spacebar.Cdn.Migration\Spacebar.Cdn.Migration.csproj", "{2B18EB6B-331D-41A9-A9D1-C2518E2AD914}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spacebar.Interop.Cdn.Signing", "Interop\Spacebar.Interop.Cdn.Signing\Spacebar.Interop.Cdn.Signing.csproj", "{95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -243,6 +247,30 @@ Global {B494C58D-2D53-49F4-9E3E-1DCF60828802}.Release|x64.Build.0 = Release|Any CPU {B494C58D-2D53-49F4-9E3E-1DCF60828802}.Release|x86.ActiveCfg = Release|Any CPU {B494C58D-2D53-49F4-9E3E-1DCF60828802}.Release|x86.Build.0 = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|x64.Build.0 = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Debug|x86.Build.0 = Debug|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|Any CPU.Build.0 = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|x64.ActiveCfg = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|x64.Build.0 = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|x86.ActiveCfg = Release|Any CPU + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914}.Release|x86.Build.0 = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|x64.ActiveCfg = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|x64.Build.0 = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|x86.ActiveCfg = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Debug|x86.Build.0 = Debug|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|Any CPU.Build.0 = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|x64.ActiveCfg = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|x64.Build.0 = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|x86.ActiveCfg = Release|Any CPU + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -262,5 +290,7 @@ Global {2D8F75C2-C4DC-4DBC-A880-5BF021D637E5} = {16DBEA54-D51A-4D91-84DF-C701B6B4786F} {05B1FEDA-1112-49C8-B7E8-539B5ED8C05D} = {04787943-EBB6-4DE4-96D5-4CFB4A2CEE99} {B494C58D-2D53-49F4-9E3E-1DCF60828802} = {16DBEA54-D51A-4D91-84DF-C701B6B4786F} + {2B18EB6B-331D-41A9-A9D1-C2518E2AD914} = {04787943-EBB6-4DE4-96D5-4CFB4A2CEE99} + {95A8E8CC-53B2-4FC6-B41E-196BE5F96BB5} = {16DBEA54-D51A-4D91-84DF-C701B6B4786F} EndGlobalSection EndGlobal diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Fsck/Program.cs b/extra/admin-api/Utilities/Spacebar.Cdn.Fsck/Program.cs
index 15822cc9..2f9e2c57 100644 --- a/extra/admin-api/Utilities/Spacebar.Cdn.Fsck/Program.cs +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Fsck/Program.cs
@@ -5,7 +5,8 @@ using Spacebar.Interop.Cdn.Abstractions; using Spacebar.Models.Db.Contexts; var builder = Host.CreateApplicationBuilder(args); -builder.Services.AddSingleton<IFileSource>(new ProxyFileSource("http://cdn.old.server.spacebar.chat")); +// builder.Services.AddSingleton<IFileSource>(new ProxyFileSource("http://cdn.old.server.spacebar.chat")); +builder.Services.AddSingleton<IFileSource>(new FilesystemFileSource("/mnt/data/dedicated/spacebar-storage")); builder.Services.AddSingleton<LruFileCache>(new LruFileCache(1*1024*1024*1024)); builder.Services.AddHostedService<FsckService>(); @@ -18,4 +19,5 @@ builder.Services.AddDbContextPool<SpacebarDbContext>(options => { StreamingHttpClient.LogRequests = false; var host = builder.Build(); +await host.Services.GetRequiredService<IFileSource>().Init(); host.Start(); \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/FsckService.cs b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/FsckService.cs new file mode 100644
index 00000000..fbfd3478 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/FsckService.cs
@@ -0,0 +1,210 @@ +using System.Diagnostics; +using ArcaneLibs.Extensions; +using Microsoft.EntityFrameworkCore; +using Spacebar.Interop.Cdn.Signing; +using Spacebar.Models.Db.Contexts; + +namespace Spacebar.Cdn.Fsck; + +public class FsckService(ILogger<FsckService> logger, IServiceScopeFactory serviceScopeFactory, MigrationFileStores stores, CdnSigningService sigService) : IHostedService { + private SpacebarDbContext _db = null!; + + public async Task StartAsync(CancellationToken cancellationToken) { + var sw = Stopwatch.StartNew(); + await using var scope = serviceScopeFactory.CreateAsyncScope(); + _db = scope.ServiceProvider.GetRequiredService<SpacebarDbContext>(); + logger.LogInformation("Starting migrations from {source} to {dest}...", $"{stores.From.GetType().FullName}({stores.From.BaseUrl})", + $"{stores.To.GetType().FullName}({stores.To.BaseUrl})"); + await RunFsckAsync("User Avatars", "/avatars", EnumerateUserAvatarFilesAsync(), cancellationToken); + await RunFsckAsync("User Banners", "/banners", EnumerateUserBannerPathsAsync(), cancellationToken); + await RunFsckAsync("Guild Icons", "/icons", EnumerateGuildIconPathsAsync(), cancellationToken); + await RunFsckAsync("Stickers", "/stickers", EnumerateStickerPathsAsync(), cancellationToken); + await RunFsckAsync("Emojis", "/emojis", EnumerateEmojiPathsAsync(), cancellationToken); + + // var atts = EnumerateAttachmentPathsAsync(); + // var refreshedAtts = new List<FsckItem>(); + // var attRefreshedTasks = atts.Chunk(40).Select(async x => { + // var req = + // return new FsckItem[10]; + // }); + // await foreach (var attRefreshedChunk in attRefreshedTasks.ToAsyncResultEnumerable()) { + // refreshedAtts.AddRange(attRefreshedChunk); + // } + + await RunFsckAsync("Attachments", "/attachments", EnumerateAttachmentPathsAsync(), cancellationToken); + logger.LogInformation("Fsck complete in {time}.", sw.Elapsed); + } + + public async Task StopAsync(CancellationToken cancellationToken) { } + + private readonly Stopwatch _lastUpdateSw = Stopwatch.StartNew(); + private readonly SemaphoreSlim _fsckSemaphore = new(32, 32); + + public struct FsckItem { + public string Path; + public string ItemId; + } + + private async Task RunFsckAsync(string name, string path, IQueryable<FsckItem> items, CancellationToken? cancellationToken = null) { + int i = 0, notFound = 0, alreadyLocal = 0, count = await items.CountAsync(); + List<Task> tasks = []; + + await foreach (var item in items.AsAsyncEnumerable()) { + tasks.Add(Task.Run(async () => { + try { + await _fsckSemaphore.WaitAsync(); + if (cancellationToken?.IsCancellationRequested ?? false) + return; + + if (await stores.To.FileExists(item.Path)) { + alreadyLocal++; + logger.LogInformation("TO: {itemType} {itemId} already exists at {path}, skipping.", name, item.ItemId, item.Path); + } + else if (!await stores.From.FileExists(item.Path)) { + notFound++; + logger.LogWarning("FROM: {itemType} {itemId} is missing at {path}", name, item.ItemId, stores.From.BaseUrl + item.Path); + } + else { + // logger.LogInformation("Migrating {itemType} {itemId} at {path}", name, item.ItemId, item.Path); + await using var f = await stores.From.GetFile(item.Path); + // logger.LogInformation("Got file {itemType} {itemId} at {path}, writing to destination...", name, item.ItemId, item.Path); + await stores.To.WriteFile(item.Path, f.Stream); + } + + if (true || _lastUpdateSw.ElapsedMilliseconds >= 1000 / 30 || i == 0) { + _lastUpdateSw.Restart(); + Console.Write($"{name} download: {i}/{count}: {item.Path,-64}\r"); + } + + i++; + } + catch (Exception ex) { + logger.LogError(ex, "Error processing {itemType} {itemId} at {path}: {message}", name, item.ItemId, item.Path, ex.Message); + } + finally { + _fsckSemaphore.Release(); + } + })); + } + + await Task.WhenAll(tasks); + logger.LogInformation("Validated {count} items for {path}: {alreadyLocal} already local, {notFound} not found", i, path, alreadyLocal, notFound); + } + +#region User Assets + + public IQueryable<FsckItem> EnumerateUserAvatarFilesAsync() => + _db.Users + .Where(x => !string.IsNullOrWhiteSpace(x.Avatar)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/avatars/{x.Id}/{x.Avatar}", + ItemId = x.Id + }); + + public IQueryable<FsckItem> EnumerateUserBannerPathsAsync() => + _db.Users + .Where(x => !string.IsNullOrWhiteSpace(x.Banner)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/banners/{x.Id}/{x.Banner}", + ItemId = x.Id + }); + +#endregion + +#region Guild Assets + + public IQueryable<FsckItem> EnumerateGuildIconPathsAsync() => + _db.Guilds + .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/icons/{x.Id}/{x.Icon}", + ItemId = x.Id + }); + + public IQueryable<FsckItem> EnumerateRoleIconPathsAsync() => + _db.Roles + .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/role-icons/{x.Id}/{x.Icon}", + ItemId = x.Id + }); + + public IQueryable<FsckItem> EnumerateStickerPathsAsync() => + _db.Stickers + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/stickers/{x.Id}.png", + ItemId = x.Id + }); + + public IQueryable<FsckItem> EnumerateEmojiPathsAsync() => + _db.Emojis + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/emojis/{x.Id}", + ItemId = x.Id + }); + +#endregion + +#region Application Assets + + public IQueryable<FsckItem> EnumerateApplicationIconPathsAsync() => + _db.Applications + .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/app-icons/{x.Id}/{x.Icon}", + ItemId = x.Id + }); + + public IQueryable<FsckItem> EnumerateApplicationCoverPathsAsync() => + _db.Applications + .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = $"/app-icons/{x.Id}/{x.CoverImage}", + ItemId = x.Id + }); + + // TODO: not implemented? + // public IQueryable<FsckItem> EnumerateApplicationSplashPathsAsync() => + // _db.Applications + // .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + // .OrderBy(x => x.Id) + // .Select(x => new FsckItem { + // Path = $"/app-icons/{x.Id}/{x.OwnerId}", // TODO - no db property for splash? + // ItemId = x.Id + // }); + // + // public IQueryable<FsckItem> EnumerateApplicationAssets() => + // _db.Applications + // .Where(x => !string.IsNullOrWhiteSpace(x.Icon)) + // .OrderBy(x => x.Id) + // .Select(x => new FsckItem { + // Path = $"/app-icons/{x.Id}/{x.Icon}", + // ItemId = x.Id + // }); + +#endregion + +#region Attachments + + public IQueryable<FsckItem> EnumerateAttachmentPathsAsync() => + _db.Attachments + .OrderBy(x => x.Id) + .Select(x => new FsckItem { + Path = sigService.Sign(new() { + Path = $"/attachments/{x.Message!.ChannelId}/{x.Id}/{x.Filename}", + IpAddress = "109.128.185.4" + }).GetSignedPath(), + // Path = $"/attachments/{x.Message!.ChannelId}/{x.Id}/{x.Filename}", + ItemId = x.Id + }); + +#endregion +} \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/MigrationFileStores.cs b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/MigrationFileStores.cs new file mode 100644
index 00000000..9626d70d --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/MigrationFileStores.cs
@@ -0,0 +1,8 @@ +using Spacebar.Interop.Cdn.Abstractions; + +namespace Spacebar.Cdn.Fsck; + +public class MigrationFileStores { + public required IFileSource From { get; init; } + public required IFileSource To { get; init; } +} \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Program.cs b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Program.cs new file mode 100644
index 00000000..280ffe94 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Program.cs
@@ -0,0 +1,57 @@ +using System.Buffers.Text; +using ArcaneLibs; +using Microsoft.EntityFrameworkCore; +using Spacebar.Cdn.Fsck; +using Spacebar.Interop.Cdn.Abstractions; +using Spacebar.Interop.Cdn.Signing; +using Spacebar.Models.Db.Contexts; + +var builder = Host.CreateApplicationBuilder(args); +// builder.Services.AddSingleton<IFileSource>(new ProxyFileSource("http://cdn.old.server.spacebar.chat")); +IFileSource fromSrc, toSrc; + +if (args.Length != 2) { + Console.WriteLine("Usage: Spacebar.Cdn.Migration <from-path> <to-path>"); + return; +} + +if (args[0].StartsWith("http://")) + fromSrc = new ProxyFileSource(args[0]); +else + fromSrc = new FilesystemFileSource(args[0]); + +if (args[1].StartsWith("http://")) + toSrc = new ProxyFileSource(args[1]); +else + toSrc = new FilesystemFileSource(args[1]); + +builder.Services.AddSingleton(new MigrationFileStores() { + From = fromSrc, + To = toSrc +}); + +builder.Services.AddSingleton<CdnSigningService>(sp => + new CdnSigningService( + sp.GetRequiredService<ILogger<CdnSigningService>>(), + Convert.FromBase64String(""), + false, + true, + TimeSpan.FromMinutes(5) + ) +); +builder.Services.AddHostedService<FsckService>(); + +builder.Services.AddDbContextPool<SpacebarDbContext>(options => { + options + .UseNpgsql(builder.Configuration.GetConnectionString("Spacebar")) + .EnableDetailedErrors(); +}); + +StreamingHttpClient.LogRequests = false; + +var host = builder.Build(); + +await host.Services.GetRequiredService<MigrationFileStores>().From.Init(); +await host.Services.GetRequiredService<MigrationFileStores>().To.Init(); + +host.Start(); \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Properties/launchSettings.json b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Properties/launchSettings.json new file mode 100644
index 00000000..d2903516 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Properties/launchSettings.json
@@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Local": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + } + } +} diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Spacebar.Cdn.Migration.csproj b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Spacebar.Cdn.Migration.csproj new file mode 100644
index 00000000..1791cd1f --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/Spacebar.Cdn.Migration.csproj
@@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk.Worker"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <UserSecretsId>dotnet-Spacebar.Cdn.Fsck-a4aee86f-7e64-4c71-88d7-d2a76cc8b77e</UserSecretsId> + <NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.2"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\Interop\Spacebar.Interop.Cdn.Abstractions\Spacebar.Interop.Cdn.Abstractions.csproj" /> + <ProjectReference Include="..\..\Interop\Spacebar.Interop.Cdn.Signing\Spacebar.Interop.Cdn.Signing.csproj" /> + <ProjectReference Include="..\..\Models\Spacebar.Models.Db\Spacebar.Models.Db.csproj" /> + </ItemGroup> +</Project> diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.Development.json b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.Development.json new file mode 100644
index 00000000..b2dcdb67 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.Development.json
@@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.json b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.json new file mode 100644
index 00000000..b2dcdb67 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/appsettings.json
@@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/extra/admin-api/Utilities/Spacebar.Cdn.Migration/deps.json b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/deps.json new file mode 100644
index 00000000..516b87b2 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.Cdn.Migration/deps.json
@@ -0,0 +1,332 @@ +[ + { + "pname": "ArcaneLibs", + "version": "1.0.1-preview.20260126-091403", + "hash": "sha256-CSmNE16nDi05qyDAcJR+8SqQQ2ReAeX0+/dRP3WpNsg=" + }, + { + "pname": "Humanizer.Core", + "version": "2.14.1", + "hash": "sha256-EXvojddPu+9JKgOG9NSQgUTfWq1RpOYw7adxDPKDJ6o=" + }, + { + "pname": "Microsoft.Build.Framework", + "version": "17.11.31", + "hash": "sha256-YS4oASrmC5dmZrx5JPS7SfKmUpIJErlUpVDsU3VrfFE=" + }, + { + "pname": "Microsoft.Build.Framework", + "version": "18.0.2", + "hash": "sha256-fO31KAdDs2J0RUYD1ov9UB3ucsbALan7K0YdWW+yg7A=" + }, + { + "pname": "Microsoft.CodeAnalysis.Analyzers", + "version": "3.11.0", + "hash": "sha256-hQ2l6E6PO4m7i+ZsfFlEx+93UsLPo4IY3wDkNG11/Sw=" + }, + { + "pname": "Microsoft.CodeAnalysis.Common", + "version": "5.0.0", + "hash": "sha256-g4ALvBSNyHEmSb1l5TFtWW7zEkiRmhqLx4XWZu9sr2U=" + }, + { + "pname": "Microsoft.CodeAnalysis.CSharp", + "version": "5.0.0", + "hash": "sha256-ctBCkQGFpH/xT5rRE3xibu9YxPD108RuC4a4Z25koG8=" + }, + { + "pname": "Microsoft.CodeAnalysis.CSharp.Workspaces", + "version": "5.0.0", + "hash": "sha256-yWVcLt/f2CouOfFy966glGdtSFy+RcgrU1dd9UtlL/Q=" + }, + { + "pname": "Microsoft.CodeAnalysis.Workspaces.Common", + "version": "5.0.0", + "hash": "sha256-Bir5e1gEhgQQ6upQmVKQHAKLRfenAu60DAzNupNnZsQ=" + }, + { + "pname": "Microsoft.CodeAnalysis.Workspaces.MSBuild", + "version": "5.0.0", + "hash": "sha256-+58+iqTayTiE0pDaog1U8mjaDA8bNNDLA8gjCQZZudo=" + }, + { + "pname": "Microsoft.EntityFrameworkCore", + "version": "10.0.0", + "hash": "sha256-xfgrlxhtOkQwF5Q7j8gSm41URJiH8IuJ/T/Dh88++hE=" + }, + { + "pname": "Microsoft.EntityFrameworkCore", + "version": "10.0.2", + "hash": "sha256-FS6T8EnaWCMtj4PnZhh+oF8mcM44VlM3wkTSMlpte9A=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Abstractions", + "version": "10.0.2", + "hash": "sha256-qkDfIJpcPO2kk4n5OE/13hI/0mUygpTofInn95XjRZI=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Analyzers", + "version": "10.0.2", + "hash": "sha256-yOv78rgAACBz1zjitpcZbQQ3zx8huJongZTHkhN4PQ0=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Design", + "version": "10.0.2", + "hash": "sha256-bTShsGux0y/49PIIMb/4ZX3x5+rPacvT5/NcooNCI1Y=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Relational", + "version": "10.0.0", + "hash": "sha256-vOP2CE5YA551BlpbOuIy6RuAiAEPEpCVS1cEE33/zN4=" + }, + { + "pname": "Microsoft.EntityFrameworkCore.Relational", + "version": "10.0.2", + "hash": "sha256-Y4jPpoYhKizg5wF6QfkBX4sYlE2FU1bYhfoDN3xkhKM=" + }, + { + "pname": "Microsoft.Extensions.Caching.Abstractions", + "version": "10.0.2", + "hash": "sha256-nKmQuZTt1g5/8gBajo7wdCV64kdCucdiQR8JTt7ZZb0=" + }, + { + "pname": "Microsoft.Extensions.Caching.Memory", + "version": "10.0.2", + "hash": "sha256-sRUF7DM0s1yzZnfjM/hF9A/IysE6Er23gZ6jST+RWh0=" + }, + { + "pname": "Microsoft.Extensions.Configuration", + "version": "10.0.2", + "hash": "sha256-dBJAKDyp/sm+ZSMQfH0+4OH8Jnv1s20aHlWS6HNnH+c=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Abstractions", + "version": "10.0.2", + "hash": "sha256-P+0kaDGO+xB9KxF9eWHDJ4hzi05sUGM/uMNEX5NdBTE=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Binder", + "version": "10.0.2", + "hash": "sha256-resI9gIxHh2cc+258/i+TjW8xxzKf4ZBTLIcWAMEYz0=" + }, + { + "pname": "Microsoft.Extensions.Configuration.CommandLine", + "version": "10.0.2", + "hash": "sha256-2+bwt7N721EO3/uy2rnungaFguA2WNJ7pO1XKVClPKw=" + }, + { + "pname": "Microsoft.Extensions.Configuration.EnvironmentVariables", + "version": "10.0.2", + "hash": "sha256-tjqCocpGVX4jg6z8VXjjooOqegcoqJA0sjIIslQPLyU=" + }, + { + "pname": "Microsoft.Extensions.Configuration.FileExtensions", + "version": "10.0.2", + "hash": "sha256-oOHg4UShuRyaflHJL1YLbkHSlwQtpZW2wONnsTKKiE4=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Json", + "version": "10.0.2", + "hash": "sha256-RDfVyrOP9E5kVax3VswqJzPFTHSm3MZ45gSRLO0qFWs=" + }, + { + "pname": "Microsoft.Extensions.Configuration.UserSecrets", + "version": "10.0.2", + "hash": "sha256-O7yda5ebH81nOAOVOrW+CaqtPcsJmWscLV41T40hHHk=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection", + "version": "10.0.2", + "hash": "sha256-/9UWQRAI2eoocnJWWf1ktnAx/1Gt65c16fc0Xqr9+CQ=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection", + "version": "9.0.0", + "hash": "sha256-dAH52PPlTLn7X+1aI/7npdrDzMEFPMXRv4isV1a+14k=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "10.0.2", + "hash": "sha256-UF9T13V5SQxJy2msfLmyovLmitZrjJayf8gHH+uK2eg=" + }, + { + "pname": "Microsoft.Extensions.DependencyModel", + "version": "10.0.2", + "hash": "sha256-w/dGIjtZiGH+KW3969BPOdQpQEV+WB7RPTa2MK2DavE=" + }, + { + "pname": "Microsoft.Extensions.Diagnostics", + "version": "10.0.2", + "hash": "sha256-Zb/qJA0cZYiPQO7I3AegZCcNT0aJlKTycUP11Mqbm6o=" + }, + { + "pname": "Microsoft.Extensions.Diagnostics.Abstractions", + "version": "10.0.2", + "hash": "sha256-Aob6wq51LdquE7SkkxtCzcuHBKWrJcb3Ebi/dU3aqA4=" + }, + { + "pname": "Microsoft.Extensions.FileProviders.Abstractions", + "version": "10.0.2", + "hash": "sha256-tibCkkT9WliU2E/i0ufx7/Va6H6QZX4hR/1oUp8ecgQ=" + }, + { + "pname": "Microsoft.Extensions.FileProviders.Physical", + "version": "10.0.2", + "hash": "sha256-HtzaL9KEFkyIyG31RIjj57yFBw6Ja3U6UYLP0LLesvw=" + }, + { + "pname": "Microsoft.Extensions.FileSystemGlobbing", + "version": "10.0.2", + "hash": "sha256-WHqwQLBlb4w+fw1KZVPvunir5cM/4jXExWRofgfVAxs=" + }, + { + "pname": "Microsoft.Extensions.Hosting", + "version": "10.0.2", + "hash": "sha256-q+GiN081aRi0690nHVhWmAynfokQINPuvGzKiXh6oIM=" + }, + { + "pname": "Microsoft.Extensions.Hosting.Abstractions", + "version": "10.0.2", + "hash": "sha256-mkeKUXepn4bfEdZFXdURmNEFdGiHQdpcxnm6joG+pUA=" + }, + { + "pname": "Microsoft.Extensions.Logging", + "version": "10.0.2", + "hash": "sha256-9+gfQwK32JMYscW1YvyCWEzc9mRZOjCACoD9U1vVaJI=" + }, + { + "pname": "Microsoft.Extensions.Logging", + "version": "9.0.0", + "hash": "sha256-kR16c+N8nQrWeYLajqnXPg7RiXjZMSFLnKLEs4VfjcM=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "10.0.0", + "hash": "sha256-BnhgGZc01HwTSxogavq7Ueq4V7iMA3wPnbfRwQ4RhGk=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "10.0.2", + "hash": "sha256-ndKGzq8+2J/hvaIULwBui0L/jDyMQTAY24j+ohX5VX8=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "9.0.0", + "hash": "sha256-iBTs9twjWXFeERt4CErkIIcoJZU1jrd1RWCI8V5j7KU=" + }, + { + "pname": "Microsoft.Extensions.Logging.Configuration", + "version": "10.0.2", + "hash": "sha256-eSfmagvrcdRVmTXEdzfvWEDNBewB/YClOGSDq4gThk0=" + }, + { + "pname": "Microsoft.Extensions.Logging.Console", + "version": "10.0.2", + "hash": "sha256-mfaypUwMZxEIKBfnmzhnCScC/uPpZBRYN+/2SNcxcu4=" + }, + { + "pname": "Microsoft.Extensions.Logging.Debug", + "version": "10.0.2", + "hash": "sha256-hcHfYTWyQzRHEkuo9ejkUZxfJC0+JMLOgq51jpDJwOI=" + }, + { + "pname": "Microsoft.Extensions.Logging.EventLog", + "version": "10.0.2", + "hash": "sha256-LFbcqm2JfDowycOiVWNzWrF0vAsP3ug6c1njt373nE4=" + }, + { + "pname": "Microsoft.Extensions.Logging.EventSource", + "version": "10.0.2", + "hash": "sha256-1vp78PlJpE+zyWknU7imDTq0mC8Fk0wU11DqDRuuEF4=" + }, + { + "pname": "Microsoft.Extensions.Options", + "version": "10.0.2", + "hash": "sha256-12AfUEDdta/pmZUyEyqSUfOk0YoA7JOfGmIYnZQ//qk=" + }, + { + "pname": "Microsoft.Extensions.Options", + "version": "9.0.0", + "hash": "sha256-DT5euAQY/ItB5LPI8WIp6Dnd0lSvBRP35vFkOXC68ck=" + }, + { + "pname": "Microsoft.Extensions.Options.ConfigurationExtensions", + "version": "10.0.2", + "hash": "sha256-WJahsWyT5wYdLPEJufHKpb3l/dl7D2iw2SnMK0Jr53U=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "10.0.2", + "hash": "sha256-8Ccrjjv9cFVf9RyCc7GS/Byt8+DXdSNea0UX3A5BEdA=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "9.0.0", + "hash": "sha256-ZNLusK1CRuq5BZYZMDqaz04PIKScE2Z7sS2tehU7EJs=" + }, + { + "pname": "Microsoft.VisualStudio.SolutionPersistence", + "version": "1.0.52", + "hash": "sha256-KZGPtOXe6Hv8RrkcsgoLKTRyaCScIpQEa2NhNB3iOXw=" + }, + { + "pname": "Mono.TextTemplating", + "version": "3.0.0", + "hash": "sha256-VlgGDvgNZb7MeBbIZ4DE2Nn/j2aD9k6XqNHnASUSDr0=" + }, + { + "pname": "Newtonsoft.Json", + "version": "13.0.3", + "hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc=" + }, + { + "pname": "Npgsql", + "version": "10.0.0", + "hash": "sha256-UVKz9dH/rVCCbMyFdqA31RYpht1XgDRLIqUy0Dp9ACQ=" + }, + { + "pname": "Npgsql.EntityFrameworkCore.PostgreSQL", + "version": "10.0.0", + "hash": "sha256-XIJxnTMektQVP1qtslEIGbmBGrIQsvjQjCMRTs9UIbg=" + }, + { + "pname": "System.CodeDom", + "version": "6.0.0", + "hash": "sha256-uPetUFZyHfxjScu5x4agjk9pIhbCkt5rG4Axj25npcQ=" + }, + { + "pname": "System.Composition", + "version": "9.0.0", + "hash": "sha256-FehOkQ2u1p8mQ0/wn3cZ+24HjhTLdck8VZYWA1CcgbM=" + }, + { + "pname": "System.Composition.AttributedModel", + "version": "9.0.0", + "hash": "sha256-a7y7H6zj+kmYkllNHA402DoVfY9IaqC3Ooys8Vzl24M=" + }, + { + "pname": "System.Composition.Convention", + "version": "9.0.0", + "hash": "sha256-tw4vE5JRQ60ubTZBbxoMPhtjOQCC3XoDFUH7NHO7o8U=" + }, + { + "pname": "System.Composition.Hosting", + "version": "9.0.0", + "hash": "sha256-oOxU+DPEEfMCuNLgW6wSkZp0JY5gYt44FJNnWt+967s=" + }, + { + "pname": "System.Composition.Runtime", + "version": "9.0.0", + "hash": "sha256-AyIe+di1TqwUBbSJ/sJ8Q8tzsnTN+VBdJw4K8xZz43s=" + }, + { + "pname": "System.Composition.TypedParts", + "version": "9.0.0", + "hash": "sha256-F5fpTUs3Rr7yP/NyIzr+Xn5NdTXXp8rrjBnF9UBBUog=" + }, + { + "pname": "System.Diagnostics.EventLog", + "version": "10.0.2", + "hash": "sha256-rezZk0M4+MWRxwGSFzXfvekrhL8qLTp7Pc8YsSy/4nE=" + } +]