mirror of
https://github.com/spacebarchat/server.git
synced 2026-05-25 09:54:34 +00:00
nix/cdn work
This commit is contained in:
Generated
+62
-43
@@ -17,6 +17,13 @@
|
||||
<entry key="_/home/Rory/git/spacebar/server-master" value="2oJ9u2nkEFq1qQW1NFF69ECjiYu" />
|
||||
</persistenceIdMap>
|
||||
</component>
|
||||
<component name="CopilotUserSelectedModel">
|
||||
<selectedModels>
|
||||
<entry key="edit-panel" value="GPT-5 mini" />
|
||||
<entry key="chat-panel" value="Gemini 3 Pro (Preview)" />
|
||||
<entry key="agent-panel" value="Gemini 3 Pro (Preview)" />
|
||||
</selectedModels>
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
@@ -43,6 +50,9 @@
|
||||
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/WhereExpressionBuilder.d.ts" root0="SKIP_INSPECTION" />
|
||||
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/repository/BaseEntity.d.ts" root0="SKIP_INSPECTION" />
|
||||
</component>
|
||||
<component name="NamedScopeManager">
|
||||
<scope name="src" pattern="!file[server]:*/||file[server]:src//*" />
|
||||
</component>
|
||||
<component name="ProblemsViewState">
|
||||
<option name="selectedTabId" value="ProjectErrors" />
|
||||
</component>
|
||||
@@ -55,52 +65,52 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix",
|
||||
"NIXITCH_NIX_CONF_DIR": "",
|
||||
"NIXITCH_NIX_OTHER_STORES": "",
|
||||
"NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source",
|
||||
"NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile",
|
||||
"NIXITCH_NIX_REMOTE": "",
|
||||
"NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory",
|
||||
"Node.js.Server.ts.executor": "Debug",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"javascript.nodejs.core.library.configured.version": "24.11.1",
|
||||
"javascript.nodejs.core.library.typings.version": "24.10.9",
|
||||
"last_opened_file_path": "/home/Rory/git/spacebar/server-master/nix/modules/default",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.standard": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.standard": "",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_interpreter_path": "node",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"npm.Start API.executor": "Run",
|
||||
"npm.Start CDN.executor": "Run",
|
||||
"npm.Start Gateway.executor": "Run",
|
||||
"npm.build.executor": "Run",
|
||||
"npm.build:src.executor": "Run",
|
||||
"npm.build:src:tsgo.executor": "Run",
|
||||
"npm.build:tsgo.executor": "Run",
|
||||
"npm.start.executor": "Debug",
|
||||
"prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier",
|
||||
"ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix",
|
||||
"NIXITCH_NIX_CONF_DIR": "",
|
||||
"NIXITCH_NIX_OTHER_STORES": "",
|
||||
"NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source",
|
||||
"NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile",
|
||||
"NIXITCH_NIX_REMOTE": "",
|
||||
"NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory",
|
||||
"Node.js.Server.ts.executor": "Debug",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"javascript.nodejs.core.library.configured.version": "24.11.1",
|
||||
"javascript.nodejs.core.library.typings.version": "24.10.9",
|
||||
"last_opened_file_path": "/home/Rory/git/spacebar/server-master/nix/modules/default",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.standard": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.standard": "",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_interpreter_path": "node",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"npm.Start API.executor": "Run",
|
||||
"npm.Start CDN.executor": "Run",
|
||||
"npm.Start Gateway.executor": "Run",
|
||||
"npm.build.executor": "Run",
|
||||
"npm.build:src.executor": "Run",
|
||||
"npm.build:src:tsgo.executor": "Run",
|
||||
"npm.build:tsgo.executor": "Run",
|
||||
"npm.start.executor": "Debug",
|
||||
"prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier",
|
||||
"ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
],
|
||||
"GitStage.ChangesTree.GroupingKeys": [
|
||||
"directory",
|
||||
"module",
|
||||
"repository"
|
||||
"GitStage.ChangesTree.GroupingKeys": [
|
||||
"directory",
|
||||
"module",
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/nix/modules/default" />
|
||||
@@ -191,7 +201,8 @@
|
||||
<workItem from="1769278836997" duration="449000" />
|
||||
<workItem from="1771558270982" duration="82802000" />
|
||||
<workItem from="1771818832744" duration="44139000" />
|
||||
<workItem from="1771996450006" duration="61321000" />
|
||||
<workItem from="1771996450006" duration="81615000" />
|
||||
<workItem from="1772272118101" duration="20496000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
@@ -230,4 +241,12 @@
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
</component>
|
||||
<component name="github-copilot-workspace">
|
||||
<instructionFileLocations>
|
||||
<option value=".github/instructions" />
|
||||
</instructionFileLocations>
|
||||
<promptFileLocations>
|
||||
<option value=".github/prompts" />
|
||||
</promptFileLocations>
|
||||
</component>
|
||||
</project>
|
||||
@@ -7,25 +7,17 @@ using Spacebar.Interop.Cdn.Abstractions;
|
||||
namespace Spacebar.Cdn.Controllers;
|
||||
|
||||
[ApiController]
|
||||
public class GetImageController(LruFileCache lfc, IFileSource fs, DiscordImageResizeService dirs) : ControllerBase {
|
||||
[HttpGet("/avatars/{_:required}")]
|
||||
public class GetImageController(LruFileCache lfc, IFileSource fs, DiscordImageResizeService dirs) : ImageController {
|
||||
// [HttpGet("/avatars/{_:required}")]
|
||||
[HttpGet("/emojis/{emoji_id:required}.{ext:required}")]
|
||||
[HttpGet("/stickers/{sticker_id:required}.{ext:required}")]
|
||||
[HttpGet("/avatars/{user_id:required}/{avatar_hash:required}.{ext:required}")]
|
||||
// [HttpGet("/avatars/{user_id:required}/{avatar_hash:required}.{ext:required}")]
|
||||
[HttpGet("/banners/{user_id:required}/{user_banner:required}.{ext:required}")]
|
||||
public async Task<IActionResult> GetImage(string? ext) {
|
||||
var originalKey = fs.BaseUrl + Request.Path;
|
||||
var cacheKey = Request.Path + Request.QueryString;
|
||||
|
||||
DiscordImageResizeParams resizeParams = new() {
|
||||
Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) ? size : null,
|
||||
Quality = Request.Query.ContainsKey("quality") && Enum.TryParse<DiscordImageResizeQuality>(Request.Query["quality"], true, out var quality) ? quality : DiscordImageResizeQuality.High,
|
||||
KeepAspectRatio = !Request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(Request.Query["keepAspectRatio"], out bool kar) || kar,
|
||||
Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt,
|
||||
Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an,
|
||||
SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au,
|
||||
SpacebarOptimiseGif = Request.Query.ContainsKey("optimiseGif") && bool.TryParse(Request.Query["optimiseGif"], out bool og) && og
|
||||
};
|
||||
|
||||
DiscordImageResizeParams resizeParams = GetResizeParams();
|
||||
|
||||
var entry = await lfc.GetOrAdd(cacheKey, async () => {
|
||||
var original = await fs.GetFile(Request.Path);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Immutable;
|
||||
using ArcaneLibs.Extensions.Streams;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Spacebar.AdminApi.TestClient.Services.Services;
|
||||
using Spacebar.Cdn.Extensions;
|
||||
using Spacebar.Interop.Cdn.Abstractions;
|
||||
|
||||
namespace Spacebar.Cdn.Controllers;
|
||||
|
||||
[ApiController]
|
||||
public class StaticAssetController(LruFileCache lfc, IFileSource fs, DiscordImageResizeService dirs) : ImageController {
|
||||
private static readonly Dictionary<string, string> defaultAvatarHashMap = new() {
|
||||
{ "0", "4a8562cf00887030c416d3ec2d46385a" },
|
||||
{ "1", "9b0bb198936784c45c72833cc426cc55" },
|
||||
{ "2", "22341bdb500c7b63a93bbce957d1601e" },
|
||||
{ "3", "d9977836b82058bf2f74eebd50edc095" },
|
||||
{ "4", "9d6ddb4e4d899a533a8cc617011351c9" },
|
||||
{ "5", "7213ab6677377974697dfdfbaf5f6a6f" },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> defaultGroupDMAvatarHashMap = new() {
|
||||
{ "0", "3b70bb66089c60f8be5e214bf8574c9d" },
|
||||
{ "1", "9581acd31832465bdeaa5385b0e919a3" },
|
||||
{ "2", "a8a4727cf2dc2939bd3c657fad4463fa" },
|
||||
{ "3", "2e46fe14586f8e95471c0917f56726b5" },
|
||||
{ "4", "fac7e78de9753d4a37083bba74c1d9ef" },
|
||||
{ "5", "4ab900144b0865430dc9be825c838faa" },
|
||||
{ "6", "1276374a404452756f3c9cc2601508a5" },
|
||||
{ "7", "904bf9f1b61f53ef4a3b7a893afeabe3" },
|
||||
};
|
||||
// [HttpGet("/embed/avatars/{userIndex}")]
|
||||
// public async Task<IActionResult> GetDefaultUserAvatar(string userIndex) {
|
||||
//
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using ArcaneLibs.Extensions.Streams;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Spacebar.AdminApi.TestClient.Services.Services;
|
||||
using Spacebar.Cdn.Extensions;
|
||||
using Spacebar.Interop.Cdn.Abstractions;
|
||||
|
||||
namespace Spacebar.Cdn.Controllers;
|
||||
|
||||
[ApiController]
|
||||
public class UserController(LruFileCache lfc, IFileSource fs, DiscordImageResizeService dirs) : ImageController {
|
||||
[HttpGet("/avatars/{userId}/{hash}.{ext}")]
|
||||
public async Task<IActionResult> GetUserAvatar(string userId, string hash, string ext) {
|
||||
var originalKey = fs.BaseUrl + Request.Path;
|
||||
var cacheKey = Request.Path + Request.QueryString;
|
||||
|
||||
DiscordImageResizeParams resizeParams = GetResizeParams();
|
||||
|
||||
var entry = await lfc.GetOrAdd(cacheKey, async () => {
|
||||
var original = await fs.GetFile(Request.Path);
|
||||
|
||||
if (Request.Query.Any()) {
|
||||
using var img = await original.ToMagickImageCollectionAsync();
|
||||
dirs.Apply(img, resizeParams);
|
||||
|
||||
var outStream = new MemoryStream();
|
||||
await img.WriteAsync(outStream, img.First().Format);
|
||||
outStream.Position = 0;
|
||||
|
||||
return new LruFileCache.Entry() {
|
||||
Data = outStream.ReadToEnd().ToArray(),
|
||||
MimeType = original.MimeType
|
||||
};
|
||||
}
|
||||
|
||||
return new LruFileCache.Entry() {
|
||||
Data = original.Stream.ReadToEnd().ToArray(),
|
||||
MimeType = original.MimeType
|
||||
};
|
||||
});
|
||||
|
||||
// byte array with mime type result
|
||||
return new FileContentResult(entry.Data, entry.MimeType);
|
||||
}
|
||||
[HttpGet("/banners/{userId}/{hash}.{ext}")]
|
||||
public async Task<IActionResult> GetUserBanner(string userId, string hash, string ext) {
|
||||
var originalKey = fs.BaseUrl + Request.Path;
|
||||
var cacheKey = Request.Path + Request.QueryString;
|
||||
|
||||
DiscordImageResizeParams resizeParams = GetResizeParams();
|
||||
|
||||
var entry = await lfc.GetOrAdd(cacheKey, async () => {
|
||||
var original = await fs.GetFile(Request.Path);
|
||||
|
||||
if (Request.Query.Any()) {
|
||||
using var img = await original.ToMagickImageCollectionAsync();
|
||||
dirs.Apply(img, resizeParams);
|
||||
|
||||
var outStream = new MemoryStream();
|
||||
await img.WriteAsync(outStream, img.First().Format);
|
||||
outStream.Position = 0;
|
||||
|
||||
return new LruFileCache.Entry() {
|
||||
Data = outStream.ReadToEnd().ToArray(),
|
||||
MimeType = original.MimeType
|
||||
};
|
||||
}
|
||||
|
||||
return new LruFileCache.Entry() {
|
||||
Data = original.Stream.ReadToEnd().ToArray(),
|
||||
MimeType = original.MimeType
|
||||
};
|
||||
});
|
||||
|
||||
// byte array with mime type result
|
||||
return new FileContentResult(entry.Data, entry.MimeType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Spacebar.AdminApi.TestClient.Services.Services;
|
||||
|
||||
namespace Spacebar.Cdn.Extensions;
|
||||
|
||||
public class ImageController : ControllerBase {
|
||||
protected DiscordImageResizeParams GetResizeParams() {
|
||||
return new() {
|
||||
Size = Request.Query.ContainsKey("size") && uint.TryParse(Request.Query["size"], out uint size) ? size : null,
|
||||
Quality = Request.Query.ContainsKey("quality") && Enum.TryParse<DiscordImageResizeQuality>(Request.Query["quality"], true, out var quality)
|
||||
? quality
|
||||
: DiscordImageResizeQuality.High,
|
||||
KeepAspectRatio = !Request.Query.ContainsKey("keepAspectRatio") || !bool.TryParse(Request.Query["keepAspectRatio"], out bool kar) || kar,
|
||||
Passthrough = Request.Query.ContainsKey("passthrough") && bool.TryParse(Request.Query["passthrough"], out bool pt) && pt,
|
||||
Animated = Request.Query.ContainsKey("animated") && bool.TryParse(Request.Query["animated"], out bool an) && an,
|
||||
SpacebarAllowUpscale = Request.Query.ContainsKey("allowUpscale") && bool.TryParse(Request.Query["allowUpscale"], out bool au) && au,
|
||||
SpacebarOptimiseGif = Request.Query.ContainsKey("optimiseGif") && bool.TryParse(Request.Query["optimiseGif"], out bool og) && og
|
||||
};
|
||||
}
|
||||
|
||||
protected void SetSuccessCacheHeader() {
|
||||
int cacheDuration = (int)TimeSpan.FromHours(6).TotalSeconds;
|
||||
Response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
|
||||
}
|
||||
|
||||
protected void SetFailureCacheHeader() {
|
||||
int cacheDuration = (int)TimeSpan.FromMinutes(5).TotalSeconds;
|
||||
Response.Headers.CacheControl = $"public, max-age={cacheDuration}, s-maxage={cacheDuration}, immutable";
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@ if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("APPSETTINGS_P
|
||||
builder.Configuration.AddJsonFile(Environment.GetEnvironmentVariable("APPSETTINGS_PATH")!);
|
||||
|
||||
// Add services to the container.
|
||||
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(Environment.GetEnvironmentVariable("STORAGE_PATH") ?? throw new InvalidOperationException("STORAGE_PATH not set!")));
|
||||
builder.Services.AddSingleton<LruFileCache>(new LruFileCache(1 * 1024 * 1024 * 1024));
|
||||
builder.Services.AddSingleton<PixelArtDetectionService>();
|
||||
builder.Services.AddSingleton<DiscordImageResizeService>();
|
||||
|
||||
@@ -12,31 +12,31 @@ namespace Spacebar.UApi.Controllers;
|
||||
[Route("/api/v{_}/guilds/{guildId}/stickers/")]
|
||||
public class GuildStickerController(ILogger<MessagesController> logger, SpacebarDbContext db, SpacebarAspNetAuthenticationService authService, UApiConfiguration cfg, PermissionService permService, Config sbCfg) : ControllerBase {
|
||||
// TODO proper response type
|
||||
[HttpPost]
|
||||
public async Task<Sticker> UploadGuildSticker(string guildId, MultipartFormDataContent content) {
|
||||
|
||||
var sticker = new Sticker() {
|
||||
GuildId = guildId
|
||||
};
|
||||
|
||||
foreach (var item in content) {
|
||||
switch (item.Headers.ContentDisposition.Name.Trim('"')) {
|
||||
case "name":
|
||||
sticker.Name = await item.ReadAsStringAsync();
|
||||
break;
|
||||
case "description":
|
||||
sticker.Description = await item.ReadAsStringAsync();
|
||||
break;
|
||||
case "tags":
|
||||
sticker.Tags = await item.ReadAsStringAsync();
|
||||
break;
|
||||
case "file":
|
||||
var fileContent = await item.ReadAsStreamAsync();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sticker;
|
||||
}
|
||||
// [HttpPost]
|
||||
// public async Task<Sticker> UploadGuildSticker(string guildId, MultipartFormDataContent content) {
|
||||
//
|
||||
// var sticker = new Sticker() {
|
||||
// GuildId = guildId
|
||||
// };
|
||||
//
|
||||
// foreach (var item in content) {
|
||||
// switch (item.Headers.ContentDisposition.Name.Trim('"')) {
|
||||
// case "name":
|
||||
// sticker.Name = await item.ReadAsStringAsync();
|
||||
// break;
|
||||
// case "description":
|
||||
// sticker.Description = await item.ReadAsStringAsync();
|
||||
// break;
|
||||
// case "tags":
|
||||
// sticker.Tags = await item.ReadAsStringAsync();
|
||||
// break;
|
||||
// case "file":
|
||||
// var fileContent = await item.ReadAsStreamAsync();
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return sticker;
|
||||
// }
|
||||
}
|
||||
@@ -11,42 +11,42 @@ namespace Spacebar.UApi.Controllers.Messages;
|
||||
[ApiController]
|
||||
[Route("/api/v{_}/channels/{channelId}/messages")]
|
||||
public partial class MessagesController(ILogger<MessagesController> logger, SpacebarDbContext db, SpacebarAspNetAuthenticationService authService, UApiConfiguration cfg) : ControllerBase {
|
||||
[Consumes("multipart/form-data")]
|
||||
[HttpPost]
|
||||
public async Task CreateMessageWithAttachments(string channelId, MultipartFormDataContent formData) {
|
||||
// Generic proxy doesnt work with multipart/form-data for some reason, so handle them specially
|
||||
JsonObject jsonPayload = null!;
|
||||
|
||||
foreach (var content in formData)
|
||||
{
|
||||
if (content.Headers.ContentDisposition?.Name == "payload_json") {
|
||||
jsonPayload = await content.ReadFromJsonAsync<JsonObject>();
|
||||
break;
|
||||
}
|
||||
if (FileNameRegex().IsMatch(content.Headers.ContentDisposition?.Name ?? "")) {
|
||||
|
||||
break;
|
||||
}
|
||||
throw new InvalidOperationException("Invalid multipart/form-data payload: missing payload_json or file attachments");
|
||||
}
|
||||
|
||||
var client = new StreamingHttpClient();
|
||||
var requestMessage = new HttpRequestMessage(
|
||||
new HttpMethod(Request.Method),
|
||||
cfg.FallbackApiEndpoint + Request.Path + Request.QueryString
|
||||
) {
|
||||
Content = new StreamContent(Request.Body)
|
||||
};
|
||||
Console.WriteLine(requestMessage.RequestUri);
|
||||
|
||||
var responseMessage = await client.SendUnhandledAsync(requestMessage, CancellationToken.None);
|
||||
Response.StatusCode = (int)responseMessage.StatusCode;
|
||||
|
||||
foreach (var header in responseMessage.Headers) Response.Headers[header.Key] = header.Value.ToArray();
|
||||
foreach (var header in responseMessage.Content.Headers) Response.Headers[header.Key] = header.Value.ToArray();
|
||||
|
||||
await responseMessage.Content.CopyToAsync(Response.Body);
|
||||
}
|
||||
// [Consumes("multipart/form-data")]
|
||||
// [HttpPost]
|
||||
// public async Task CreateMessageWithAttachments(string channelId, MultipartFormDataContent formData) {
|
||||
// // Generic proxy doesnt work with multipart/form-data for some reason, so handle them specially
|
||||
// JsonObject jsonPayload = null!;
|
||||
//
|
||||
// foreach (var content in formData)
|
||||
// {
|
||||
// if (content.Headers.ContentDisposition?.Name == "payload_json") {
|
||||
// jsonPayload = await content.ReadFromJsonAsync<JsonObject>();
|
||||
// break;
|
||||
// }
|
||||
// if (FileNameRegex().IsMatch(content.Headers.ContentDisposition?.Name ?? "")) {
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// throw new InvalidOperationException("Invalid multipart/form-data payload: missing payload_json or file attachments");
|
||||
// }
|
||||
//
|
||||
// var client = new StreamingHttpClient();
|
||||
// var requestMessage = new HttpRequestMessage(
|
||||
// new HttpMethod(Request.Method),
|
||||
// cfg.FallbackApiEndpoint + Request.Path + Request.QueryString
|
||||
// ) {
|
||||
// Content = new StreamContent(Request.Body)
|
||||
// };
|
||||
// Console.WriteLine(requestMessage.RequestUri);
|
||||
//
|
||||
// var responseMessage = await client.SendUnhandledAsync(requestMessage, CancellationToken.None);
|
||||
// Response.StatusCode = (int)responseMessage.StatusCode;
|
||||
//
|
||||
// foreach (var header in responseMessage.Headers) Response.Headers[header.Key] = header.Value.ToArray();
|
||||
// foreach (var header in responseMessage.Content.Headers) Response.Headers[header.Key] = header.Value.ToArray();
|
||||
//
|
||||
// await responseMessage.Content.CopyToAsync(Response.Body);
|
||||
// }
|
||||
|
||||
[GeneratedRegex(@"files\[\d+\]")]
|
||||
private static partial Regex FileNameRegex();
|
||||
|
||||
@@ -33,7 +33,7 @@ public class FsckService(ILogger<FsckService> logger, IServiceScopeFactory servi
|
||||
}
|
||||
|
||||
private async Task RunFsckAsync(string name, string path, IQueryable<FsckItem> items) {
|
||||
int i = 0, count = await items.CountAsync();
|
||||
int i = 0, count = await items.CountAsync(), missing = 0;
|
||||
List<Task> tasks = [];
|
||||
|
||||
await foreach (var item in items.AsAsyncEnumerable()) {
|
||||
@@ -46,12 +46,16 @@ public class FsckService(ILogger<FsckService> logger, IServiceScopeFactory servi
|
||||
|
||||
i++;
|
||||
if (!item.IsSingleSubDirFile) {
|
||||
if (!await fs.FileExists(item.Path))
|
||||
if (!await fs.FileExists(item.Path)) {
|
||||
logger.LogWarning("{itemType} {itemId} is missing at {path}", name, item.ItemId, item.Path);
|
||||
missing++;
|
||||
}
|
||||
}
|
||||
else if (item.IsSingleSubDirFile && fs is FilesystemFileSource ffs) {
|
||||
if(!await ffs.DirectoryExists(Path.GetDirectoryName(item.Path)))
|
||||
if (!await ffs.DirectoryExists(Path.GetDirectoryName(item.Path))) {
|
||||
logger.LogWarning("{itemType} {itemId} is missing at {path} (directory missing)", name, item.ItemId, item.Path);
|
||||
missing++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.LogWarning("Unhandled case: {itemType} {itemId} -> {path} (IsSingleSubDirFile: {isSingleSubDirFile}, fstype: {fsType})", name, item.ItemId, item.Path, item.IsSingleSubDirFile, fs.GetType().Name);
|
||||
@@ -62,7 +66,7 @@ public class FsckService(ILogger<FsckService> logger, IServiceScopeFactory servi
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
logger.LogInformation("Validated {count} items for {path}.", i, path);
|
||||
logger.LogInformation("Validated {count} items for {path}, {missing} missing.", i, path, missing);
|
||||
}
|
||||
|
||||
#region User Assets
|
||||
@@ -112,17 +116,15 @@ public class FsckService(ILogger<FsckService> logger, IServiceScopeFactory servi
|
||||
.OrderBy(x => x.Id)
|
||||
.Select(x => new FsckItem {
|
||||
Path = $"/stickers/{x.Id}",
|
||||
ItemId = x.Id,
|
||||
IsSingleSubDirFile = fs is FilesystemFileSource
|
||||
ItemId = x.Id
|
||||
});
|
||||
|
||||
public IQueryable<FsckItem> EnumerateEmojiPathsAsync() =>
|
||||
_db.Emojis
|
||||
.OrderBy(x => x.Id)
|
||||
.Select(x => new FsckItem {
|
||||
Path = $"/emojis/{x.Id}/",
|
||||
ItemId = x.Id,
|
||||
IsSingleSubDirFile = fs is FilesystemFileSource
|
||||
Path = $"/emojis/{x.Id}",
|
||||
ItemId = x.Id
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
pion-webrtc
|
||||
pion-webrtc,
|
||||
}:
|
||||
nixpkgs.lib.recursiveUpdate
|
||||
(
|
||||
@@ -102,6 +102,10 @@
|
||||
// {
|
||||
nixosModules.default = import ./nix/modules/default self;
|
||||
nixosConfigurations.testVm = import ./nix/testVm/default.nix { inherit self nixpkgs; };
|
||||
nixosConfigurations.test = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [ ];
|
||||
};
|
||||
checks =
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
@@ -125,4 +129,4 @@
|
||||
inherit self nixpkgs flake-utils;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ let
|
||||
};
|
||||
}
|
||||
// (
|
||||
if cfg.enableAdminApi then
|
||||
if cfg.adminApi.enable then
|
||||
{
|
||||
adminApi = {
|
||||
endpointPublic = "http${if cfg.adminApiEndpoint.useSsl then "s" else ""}://${cfg.adminApiEndpoint.host}:${toString cfg.adminApiEndpoint.publicPort}";
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
self:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
spacebar,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
secrets = import ../secrets.nix { inherit lib config; };
|
||||
cfg = config.services.spacebarchat-server;
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
in
|
||||
{
|
||||
imports = [ ];
|
||||
options.services.spacebarchat-server.cdnCs = lib.mkOption {
|
||||
default = { };
|
||||
description = "Configuration for C# cdn.";
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "Enable experimental C# CDN.";
|
||||
extraConfiguration = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = import ./default-appsettings-json.nix;
|
||||
description = "Extra appsettings.json configuration for the gateway offload daemon.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.cdnCs.enable (
|
||||
let
|
||||
makeServerTsService = import ../makeServerTsService.nix { inherit cfg lib secrets; };
|
||||
in
|
||||
{
|
||||
assertions = [
|
||||
(import ./assert-has-connection-string.nix "Admin API" cfg.adminApi.extraConfiguration)
|
||||
];
|
||||
|
||||
systemd.services.spacebar-cdn = makeServerTsService {
|
||||
description = "Spacebar Server - CDN (C#)";
|
||||
environment = builtins.mapAttrs (_: val: builtins.toString val) (
|
||||
{
|
||||
# things we set by default...
|
||||
EVENT_TRANSMISSION = "unix";
|
||||
EVENT_SOCKET_PATH = "/run/spacebar/";
|
||||
}
|
||||
// cfg.extraEnvironment
|
||||
// {
|
||||
# things we force...
|
||||
# CONFIG_PATH = configFile;
|
||||
CONFIG_READONLY = 1;
|
||||
ASPNETCORE_URLS = "http://0.0.0.0:${toString cfg.cdnEndpoint.localPort}";
|
||||
STORAGE_LOCATION = cfg.cdnPath;
|
||||
APPSETTINGS_PATH = jsonFormat.generate "appsettings.spacebar-cdn.json" (lib.recursiveUpdate (import ./default-appsettings-json.nix) cfg.cdnCs.extraConfiguration);
|
||||
}
|
||||
);
|
||||
serviceConfig = {
|
||||
ExecStart = "${self.packages.${pkgs.stdenv.hostPlatform.system}.Spacebar-AdminApi}/bin/Spacebar.AdminApi";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -45,6 +45,7 @@ in
|
||||
|
||||
systemd.services.spacebar-uapi = makeServerTsService {
|
||||
description = "Spacebar Server - C# API overlay";
|
||||
# after = [ "spacebar-api.service" ];
|
||||
environment = builtins.mapAttrs (_: val: builtins.toString val) (
|
||||
{
|
||||
# things we set by default...
|
||||
|
||||
@@ -18,6 +18,7 @@ in
|
||||
./integration-nginx.nix
|
||||
./users.nix
|
||||
(import ./pion-sfu.nix self)
|
||||
(import ./cs/cdn-cs.nix self)
|
||||
(import ./cs/gateway-offload-cs.nix self)
|
||||
(import ./cs/admin-api.nix self)
|
||||
(import ./cs/uapi.nix self)
|
||||
@@ -28,8 +29,6 @@ in
|
||||
in
|
||||
{
|
||||
enable = lib.mkEnableOption "Spacebar server";
|
||||
enableAdminApi = lib.mkEnableOption "Spacebar server Admin API";
|
||||
enableCdnCs = lib.mkEnableOption "Spacebar's experimental CDN rewrite";
|
||||
package = lib.mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "spacebar-server" { default = "default"; };
|
||||
databaseFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
@@ -129,6 +128,7 @@ in
|
||||
|
||||
systemd.services.spacebar-gateway = makeServerTsService {
|
||||
description = "Spacebar Server - Gateway";
|
||||
# after = [ "spacebar-api.service" ];
|
||||
environment = builtins.mapAttrs (_: val: builtins.toString val) (
|
||||
{
|
||||
# things we set by default...
|
||||
@@ -150,7 +150,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.spacebar-cdn = lib.mkIf (!cfg.enableCdnCs) (makeServerTsService {
|
||||
systemd.services.spacebar-cdn = lib.mkIf (!cfg.cdnCs.enable) (makeServerTsService {
|
||||
description = "Spacebar Server - CDN";
|
||||
environment = builtins.mapAttrs (_: val: builtins.toString val) (
|
||||
{
|
||||
|
||||
@@ -11,4 +11,11 @@
|
||||
else
|
||||
80;
|
||||
};
|
||||
|
||||
mkEndpointRaw = domain: port: publicPort: ssl: {
|
||||
host = domain;
|
||||
localPort = port;
|
||||
useSsl = ssl;
|
||||
publicPort = publicPort;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ lib.recursiveUpdate
|
||||
StateDirectoryMode = "0750";
|
||||
RuntimeDirectory = "spacebar";
|
||||
RuntimeDirectoryMode = "0750";
|
||||
RuntimeDirectoryPreserve = "yes";
|
||||
ReadWritePaths = [ cfg.cdnPath ];
|
||||
NoExecPaths = [ cfg.cdnPath ];
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
nginxTestSigning = {
|
||||
addSSL = true;
|
||||
@@ -11,72 +16,66 @@ let
|
||||
in
|
||||
{
|
||||
networking.hostName = "sbtest";
|
||||
services.nginx.virtualHosts."sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts."api.sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts."gw.sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts."cdn.sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts."webrtc.sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts."admin.sb.localhost" = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.serverName} = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.adminApiEndpoint.host} = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.apiEndpoint.host} = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.cdnEndpoint.host} = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.gatewayEndpoint.host} = nginxTestSigning;
|
||||
services.nginx.virtualHosts.${config.services.spacebarchat-server.webrtcEndpoint.host} = nginxTestSigning;
|
||||
|
||||
services.spacebarchat-server =
|
||||
let
|
||||
sbLib = import ../modules/default/lib.nix;
|
||||
csConnectionString = "Host=127.0.0.1; Username=postgres; Password=postgres; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600;";
|
||||
cfg = {
|
||||
enable = true;
|
||||
apiEndpoint = {
|
||||
useSsl = false;
|
||||
host = "api.sb.localhost";
|
||||
localPort = 3001;
|
||||
publicPort = 8080;
|
||||
};
|
||||
gatewayEndpoint = {
|
||||
useSsl = false;
|
||||
host = "gw.sb.localhost";
|
||||
localPort = 3002;
|
||||
publicPort = 8080;
|
||||
};
|
||||
cdnEndpoint = {
|
||||
useSsl = false;
|
||||
host = "cdn.sb.localhost";
|
||||
localPort = 3003;
|
||||
publicPort = 8080;
|
||||
};
|
||||
adminApiEndpoint = {
|
||||
useSsl = false;
|
||||
host = "admin.sb.localhost";
|
||||
localPort = 3004;
|
||||
publicPort = 8080;
|
||||
};
|
||||
webrtcEndpoint = {
|
||||
useSsl = false;
|
||||
host = "voice.sb.localhost";
|
||||
localPort = 3005;
|
||||
publicPort = 8080;
|
||||
};
|
||||
apiEndpoint = sbLib.mkEndpointRaw "api.sb.localhost" 3001 8080 false;
|
||||
gatewayEndpoint = sbLib.mkEndpointRaw "gw.sb.localhost" 3002 8080 false;
|
||||
cdnEndpoint = sbLib.mkEndpointRaw "cdn.sb.localhost" 3003 8080 false;
|
||||
adminApiEndpoint = sbLib.mkEndpointRaw "admin.sb.localhost" 3004 8080 false;
|
||||
webrtcEndpoint = sbLib.mkEndpointRaw "voice.sb.localhost" 3005 8080 false;
|
||||
nginx.enable = true;
|
||||
serverName = "sb.localhost";
|
||||
settings = {
|
||||
security = {
|
||||
requestSignature = "meow";
|
||||
cdnSignatureDuration = "5m";
|
||||
cdnSignatureIncludeIp = true;
|
||||
cdnSignatureIncludeUserAgent = false;
|
||||
cdnSignatureKey = "meow";
|
||||
};
|
||||
};
|
||||
|
||||
gatewayOffload = {
|
||||
enable = true;
|
||||
enableGuildSync = true;
|
||||
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
|
||||
};
|
||||
|
||||
adminApi = {
|
||||
enable = true;
|
||||
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
|
||||
};
|
||||
|
||||
cdnCs = {
|
||||
enable = false;
|
||||
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
|
||||
};
|
||||
|
||||
uApi = {
|
||||
enable = true;
|
||||
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
|
||||
};
|
||||
|
||||
pion-sfu = {
|
||||
enable = true;
|
||||
publicIp = "127.0.0.1";
|
||||
};
|
||||
|
||||
extraEnvironment = {
|
||||
DATABASE = "postgres://postgres:postgres@127.0.0.1/spacebar";
|
||||
#WEBRTC_PORT_RANGE=60000-61000;
|
||||
#PUBLIC_IP=216.230.228.60;
|
||||
LOG_REQUESTS = "-200,204,304";
|
||||
# LOG_REQUESTS = "-200,204,304";
|
||||
LOG_REQUESTS = "-";
|
||||
LOG_VALIDATION_ERRORS = true;
|
||||
#DB_LOGGING=true;
|
||||
#LOG_GATEWAY_TRACES=true;
|
||||
|
||||
Reference in New Issue
Block a user