diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index aa1b9bea69..7fc5b11e39 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,4 +1,9 @@ export default { "*.{j,t}s": [() => "npm run build:src:tsgo", "eslint --concurrency 4" /* sweet spot it seems */, "prettier --write"], - "src/schemas/{*,**/*}.ts": [() => "npm run build:src:tsgo", () => "node scripts/schema.js", () => "node scripts/openapi.js", () => "git add assets/schemas.json assets/openapi.json"], + "src/schemas/{*,**/*}.ts": [ + () => "npm run build:src:tsgo", + () => "node scripts/schema.js", + () => "node scripts/openapi.js", + () => "git add assets/schemas.json assets/openapi.json", + ], }; diff --git a/eslint.config.mjs b/eslint.config.mjs index 83ff1e783e..cd8f785e43 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -66,7 +66,7 @@ export default defineConfig([ // "sort-imports": ["error", {}], "default-case": "error", "default-case-last": "error", - "yoda": "error", + yoda: "error", // unsure what the defaults are here, but we want them to error "for-direction": "error", "constructor-super": "error", diff --git a/extra/admin-api/Models/Spacebar.Models.Db/Models/Channel.cs b/extra/admin-api/Models/Spacebar.Models.Db/Models/Channel.cs index 44dc658090..f66322ba91 100644 --- a/extra/admin-api/Models/Spacebar.Models.Db/Models/Channel.cs +++ b/extra/admin-api/Models/Spacebar.Models.Db/Models/Channel.cs @@ -23,7 +23,7 @@ public partial class Channel public string? Icon { get; set; } [Column("type")] - public int Type { get; set; } + public ChannelType Type { get; set; } [Column("last_message_id")] public long? LastMessageId { get; set; } diff --git a/extra/admin-api/Models/Spacebar.Models.Db/Models/ChannelType.cs b/extra/admin-api/Models/Spacebar.Models.Db/Models/ChannelType.cs new file mode 100644 index 0000000000..443f6358aa --- /dev/null +++ b/extra/admin-api/Models/Spacebar.Models.Db/Models/ChannelType.cs @@ -0,0 +1,24 @@ +namespace Spacebar.Models.Db.Models; + +public enum ChannelType { + GuildText = 0, + Dm = 1, + GuildVoice = 2, + GroupDm = 3, + GuildCategory = 4, + GuildNews = 5, + GuildStore = 6, + GuildLfg = 7, + LfgGroupDm = 8, + ThreadAlpha = 9, + GuildNewsThread = 10, + GuildPublicThread = 11, + GuildPrivateThread = 12, + GuildStageVoice = 13, + GuildDirectory = 14, + GuildForum = 15, + GuildMedia = 16, + Lobby = 17, + EphemeralDm = 18, + Unhandled = 255, +} diff --git a/extra/admin-api/Spacebar.AdminApi/Controllers/TestControllers/EmptyDmsController.cs b/extra/admin-api/Spacebar.AdminApi/Controllers/TestControllers/EmptyDmsController.cs index 862585b33a..406715dcb0 100644 --- a/extra/admin-api/Spacebar.AdminApi/Controllers/TestControllers/EmptyDmsController.cs +++ b/extra/admin-api/Spacebar.AdminApi/Controllers/TestControllers/EmptyDmsController.cs @@ -8,6 +8,7 @@ using Spacebar.Interop.Replication.Abstractions; using Spacebar.Models.AdminApi; using Spacebar.Models.Db.Contexts; +using Spacebar.Models.Db.Models; namespace Spacebar.AdminApi.Controllers.TestControllers; @@ -24,11 +25,10 @@ ISpacebarReplication replication public async IAsyncEnumerable GetEmptyDms() { (await auth.GetCurrentUserAsync(Request)).GetRights().AssertHasAllRights(SpacebarRights.Rights.OPERATOR); - // TODO channel type enum var channels = db.Channels .Include(x=>x.Recipients) .Include(x=>x.Messages) - .Where(x => x.Type == 1) + .Where(x => x.Type == ChannelType.Dm) .Where(x => !x.Messages.Any() && x.Recipients.Count == 1) ; @@ -51,4 +51,4 @@ public async IAsyncEnumerable GetEmptyDms() { yield break; } -} \ No newline at end of file +} diff --git a/extra/admin-api/Spacebar.Offload/Controllers/ChannelStatusController.cs b/extra/admin-api/Spacebar.Offload/Controllers/ChannelStatusController.cs index ba242680b9..88a58374e9 100644 --- a/extra/admin-api/Spacebar.Offload/Controllers/ChannelStatusController.cs +++ b/extra/admin-api/Spacebar.Offload/Controllers/ChannelStatusController.cs @@ -7,6 +7,7 @@ using Spacebar.Interop.Authentication.AspNetCore; using Spacebar.Interop.Replication.Abstractions; using Spacebar.Models.Db.Contexts; +using Spacebar.Models.Db.Models; using Spacebar.Models.Gateway; namespace Spacebar.GatewayOffload.Controllers; @@ -44,7 +45,7 @@ public async IAsyncEnumerable> GetChanne ]; foreach (var guildId in req.GuildIds ?? [req.GuildId!.Value]) { - var channels = (await db.Channels.Include(x => x.VoiceStates).Where(x => x.Type == 2 && x.GuildId == guildId && x.VoiceStates.Count > 0) + var channels = (await db.Channels.Include(x => x.VoiceStates).Where(x => x.Type == ChannelType.GuildVoice && x.GuildId == guildId && x.VoiceStates.Count > 0) .Select(x => x.Id) .ToListAsync()) .Select(x => new { @@ -65,4 +66,4 @@ public async IAsyncEnumerable> GetChanne }; } } -} \ No newline at end of file +} diff --git a/extra/admin-api/SpacebarAdminAPI.slnx b/extra/admin-api/SpacebarAdminAPI.slnx index e2ec0b0084..18f6302a9d 100644 --- a/extra/admin-api/SpacebarAdminAPI.slnx +++ b/extra/admin-api/SpacebarAdminAPI.slnx @@ -34,6 +34,9 @@ + + + diff --git a/extra/admin-api/Tests/Spacebar.Models.Db.Tests/ChannelTypeTests.cs b/extra/admin-api/Tests/Spacebar.Models.Db.Tests/ChannelTypeTests.cs new file mode 100644 index 0000000000..8358de3a51 --- /dev/null +++ b/extra/admin-api/Tests/Spacebar.Models.Db.Tests/ChannelTypeTests.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore; +using Spacebar.Models.Db.Contexts; +using Spacebar.Models.Db.Models; + +namespace Spacebar.Models.Db.Tests; + +public class ChannelTypeTests { + [Theory] + [InlineData(ChannelType.GuildText, 0)] + [InlineData(ChannelType.Dm, 1)] + [InlineData(ChannelType.GuildVoice, 2)] + [InlineData(ChannelType.GroupDm, 3)] + [InlineData(ChannelType.GuildCategory, 4)] + [InlineData(ChannelType.GuildNews, 5)] + [InlineData(ChannelType.GuildStore, 6)] + [InlineData(ChannelType.GuildLfg, 7)] + [InlineData(ChannelType.LfgGroupDm, 8)] + [InlineData(ChannelType.ThreadAlpha, 9)] + [InlineData(ChannelType.GuildNewsThread, 10)] + [InlineData(ChannelType.GuildPublicThread, 11)] + [InlineData(ChannelType.GuildPrivateThread, 12)] + [InlineData(ChannelType.GuildStageVoice, 13)] + [InlineData(ChannelType.GuildDirectory, 14)] + [InlineData(ChannelType.GuildForum, 15)] + [InlineData(ChannelType.GuildMedia, 16)] + [InlineData(ChannelType.Lobby, 17)] + [InlineData(ChannelType.EphemeralDm, 18)] + [InlineData(ChannelType.Unhandled, 255)] + public void ValuesMatchDiscordChannelTypeIds(ChannelType channelType, int expectedValue) { + Assert.Equal(expectedValue, (int)channelType); + } + + [Fact] + public void ChannelEntityUsesTypedChannelType() { + var channel = new Channel { + Type = ChannelType.Dm, + }; + + Assert.Equal(ChannelType.Dm, channel.Type); + } + + [Fact] + public async Task ChannelTypeQueriesMatchStoredEnumValues() { + await using var db = new SpacebarDbContext( + new DbContextOptionsBuilder() + .UseInMemoryDatabase($"{nameof(ChannelTypeQueriesMatchStoredEnumValues)}-{Guid.NewGuid()}") + .Options + ); + db.Channels.AddRange( + new Channel { Id = 1, Type = ChannelType.Dm }, + new Channel { Id = 2, Type = ChannelType.GroupDm }, + new Channel { Id = 3, Type = ChannelType.GuildVoice } + ); + await db.SaveChangesAsync(); + + var directMessageIds = await db.Channels + .Where(channel => channel.Type == ChannelType.Dm) + .Select(channel => channel.Id) + .ToListAsync(); + var voiceChannelIds = await db.Channels + .Where(channel => channel.Type == ChannelType.GuildVoice) + .Select(channel => channel.Id) + .ToListAsync(); + + Assert.Equal([1], directMessageIds); + Assert.Equal([3], voiceChannelIds); + } + + [Fact] + public void ChannelTypeUsesExistingIntegerDatabaseColumn() { + using var db = new SpacebarDbContext( + new DbContextOptionsBuilder() + .UseNpgsql("Host=localhost;Database=spacebar;Username=spacebar;Password=spacebar") + .Options + ); + + var channelTypeProperty = db.Model + .FindEntityType(typeof(Channel))! + .FindProperty(nameof(Channel.Type))!; + var relationalMapping = channelTypeProperty.GetRelationalTypeMapping(); + + Assert.Equal(typeof(ChannelType), channelTypeProperty.ClrType); + Assert.Equal("integer", relationalMapping.StoreType); + Assert.Equal(typeof(int), relationalMapping.Converter?.ProviderClrType); + Assert.Contains( + "WHERE c.type = 1", + db.Channels + .Where(channel => channel.Type == ChannelType.Dm) + .Select(channel => channel.Id) + .ToQueryString() + ); + } +} diff --git a/extra/admin-api/Tests/Spacebar.Models.Db.Tests/Spacebar.Models.Db.Tests.csproj b/extra/admin-api/Tests/Spacebar.Models.Db.Tests/Spacebar.Models.Db.Tests.csproj new file mode 100644 index 0000000000..92b37e8be6 --- /dev/null +++ b/extra/admin-api/Tests/Spacebar.Models.Db.Tests/Spacebar.Models.Db.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/src/cdn/util/Storage.ts b/src/cdn/util/Storage.ts index 7579e2d610..3245f65572 100644 --- a/src/cdn/util/Storage.ts +++ b/src/cdn/util/Storage.ts @@ -86,7 +86,9 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { const forcePathStyle = process.env.STORAGE_FORCE_PATH_STYLE === "true"; if (process.env.STORAGE_FORCE_PATH_STYLE === undefined) { - console.warn(`[CDN] STORAGE_FORCE_PATH_STYLE is not set for S3 provider; defaulting to virtual-hosted style. Set STORAGE_FORCE_PATH_STYLE=true to enable path-style addressing.`); + console.warn( + `[CDN] STORAGE_FORCE_PATH_STYLE is not set for S3 provider; defaulting to virtual-hosted style. Set STORAGE_FORCE_PATH_STYLE=true to enable path-style addressing.`, + ); } const { S3Storage } = require("./S3Storage");