diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9e52e96..3652ec5 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,18 +3,18 @@ "isRoot": true, "tools": { "husky": { - "version": "0.7.1", + "version": "0.7.2", "commands": [ "husky" ], "rollForward": false }, "csharpier": { - "version": "0.29.2", + "version": "0.30.6", "commands": [ "dotnet-csharpier" ], "rollForward": false } } -} \ No newline at end of file +} diff --git a/.dockerignore b/.dockerignore index f90ce74..d755b6c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,4 +20,5 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md \ No newline at end of file +README.md +static-pages/* diff --git a/.editorconfig b/.editorconfig index e6b41f9..22061dc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ resharper_not_accessed_positional_property_local_highlighting = none # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers = false -csharp_preferred_modifier_order = public, internal, protected, private, file, new, required, abstract, virtual, sealed, static, override, extern, unsafe, volatile, async, readonly:suggestion +csharp_preferred_modifier_order = public, internal, protected, private, file, new, virtual, override, required, abstract, sealed, static, extern, unsafe, volatile, async, readonly:suggestion # ReSharper properties resharper_align_multiline_binary_expressions_chain = false diff --git a/.gitignore b/.gitignore index 5a2b908..b1e845f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,16 @@ config.ini *.DotSettings.user proxy-config.json .DS_Store +.idea/.idea.Foxnouns.NET/.idea/dataSources.xml +.idea/.idea.Foxnouns.NET/.idea/sqldialects.xml docker/config.ini docker/proxy-config.json docker/frontend.env Foxnouns.DataMigrator/apps.json +migration-tools/avatar-proxy/config.json +migration-tools/avatar-migrator/.env + +out/ +build/ diff --git a/.husky/task-runner.json b/.husky/task-runner.json index 8e50f6a..72e6fea 100644 --- a/.husky/task-runner.json +++ b/.husky/task-runner.json @@ -3,15 +3,23 @@ "tasks": [ { "name": "run-prettier", - "command": "pnpm", - "args": ["format"], + "command": "npx", + "args": ["prettier", "-w", "${staged}"], + "include": [ + "Foxnouns.Frontend/**/*.ts", + "Foxnouns.Frontend/**/*.json", + "Foxnouns.Frontend/**/*.scss", + "Foxnouns.Frontend/**/*.js", + "Foxnouns.Frontend/**/*.svelte" + ], + "cwd": "Foxnouns.Frontend/", "pathMode": "absolute" }, { "name": "run-csharpier", "command": "dotnet", - "args": [ "csharpier", "${staged}" ], - "include": [ "**/*.cs" ] + "args": ["csharpier", "${staged}"], + "include": ["**/*.cs"] } ] } diff --git a/DOCKER.md b/DOCKER.md index b485eb7..b670743 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -1,10 +1,29 @@ -# Running with Docker +# Running with Docker (pre-built backend and rate limiter) *(linux/arm64 only)* + +Because SvelteKit is a pain in the ass to build in a container, and processes secrets at build time, +there is no pre-built frontend image available. +If you don't want to build images on your server, I recommend running the frontend outside of Docker. +This is preconfigured in `docker-compose.prebuilt.yml`: the backend, database, and rate limiter will run in Docker, +while the frontend is run as a normal, non-containerized service. 1. Copy `docker/config.example.ini` to `docker/config.ini`, and change the settings to your liking. 2. Copy `docker/proxy-config.example.json` to `docker/proxy-config.json`, and do the same. -3. Copy `docker/frontend.example.env` to `docker/frontend.env`, and do th esame. -4. Build with `docker compose build` -5. Run with `docker compose up` +3. Run with `docker compose up -f docker-compose.prebuilt.yml` + +The backend will listen on port 5001 and metrics will be available on port 5002. +The rate limiter (which is what should be exposed to the outside) will listen on port 5003. +You can use `docker/Caddyfile` as an example for your reverse proxy. If you use nginx, good luck. + +# Running with Docker (local builds) + +In order to run *everything* in Docker, you'll have to build every container yourself. +The advantage of this is that it's an all-in-one solution, where you only have to point your reverse proxy at a single container. +The disadvantage is that you'll likely have to build the images on the server you'll be running them on. + +1. Configure the backend and rate limiter as in the section above. +2. Copy `docker/frontend.example.env` to `docker/frontend.env`, and configure it. +3. Build with `docker compose build -f docker-compose.local.yml` +4. Run with `docker compose up -f docker-compose.local.yml` The Caddy server will listen on `localhost:5004` for the frontend and API, and on `localhost:5005` for the profile URL shortener. diff --git a/Foxnouns.Backend/Config.cs b/Foxnouns.Backend/Config.cs index e0a579b..461e55e 100644 --- a/Foxnouns.Backend/Config.cs +++ b/Foxnouns.Backend/Config.cs @@ -26,11 +26,11 @@ public class Config public string MediaBaseUrl { get; init; } = null!; public string Address => $"http://{Host}:{Port}"; - public string MetricsAddress => $"http://{Host}:{Logging.MetricsPort}"; public LoggingConfig Logging { get; init; } = new(); public DatabaseConfig Database { get; init; } = new(); public StorageConfig Storage { get; init; } = new(); + public LimitsConfig Limits { get; init; } = new(); public EmailAuthConfig EmailAuth { get; init; } = new(); public DiscordAuthConfig DiscordAuth { get; init; } = new(); public GoogleAuthConfig GoogleAuth { get; init; } = new(); @@ -54,6 +54,7 @@ public class Config public bool? EnablePooling { get; init; } public int? Timeout { get; init; } public int? MaxPoolSize { get; init; } + public string Redis { get; init; } = string.Empty; } public class StorageConfig @@ -93,4 +94,22 @@ public class Config public string? ClientId { get; init; } public string? ClientSecret { get; init; } } + + public class LimitsConfig + { + public int MaxMemberCount { get; init; } = 1000; + + public int MaxFields { get; init; } = 25; + public int MaxFieldNameLength { get; init; } = 100; + public int MaxFieldEntryTextLength { get; init; } = 100; + public int MaxFieldEntries { get; init; } = 100; + + public int MaxUsernameLength { get; init; } = 40; + public int MaxMemberNameLength { get; init; } = 100; + public int MaxDisplayNameLength { get; init; } = 100; + public int MaxLinks { get; init; } = 25; + public int MaxLinkLength { get; init; } = 256; + public int MaxBioLength { get; init; } = 1024; + public int MaxAvatarLength { get; init; } = 1_500_000; + } } diff --git a/Foxnouns.Backend/Controllers/Authentication/AuthController.cs b/Foxnouns.Backend/Controllers/Authentication/AuthController.cs index 0d95eb2..39d3b11 100644 --- a/Foxnouns.Backend/Controllers/Authentication/AuthController.cs +++ b/Foxnouns.Backend/Controllers/Authentication/AuthController.cs @@ -46,7 +46,7 @@ public class AuthController( config.GoogleAuth.Enabled, config.TumblrAuth.Enabled ); - string state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync(ct)); + string state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync()); string? discord = null; string? google = null; string? tumblr = null; diff --git a/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs b/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs index bdf4b9a..8024ee6 100644 --- a/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs +++ b/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs @@ -56,7 +56,7 @@ public class EmailAuthController( if (!req.Email.Contains('@')) throw new ApiError.BadRequest("Email is invalid", "email", req.Email); - string state = await keyCacheService.GenerateRegisterEmailStateAsync(req.Email, null, ct); + string state = await keyCacheService.GenerateRegisterEmailStateAsync(req.Email, null); // If there's already a user with that email address, pretend we sent an email but actually ignore it if ( diff --git a/Foxnouns.Backend/Controllers/Authentication/FediverseAuthController.cs b/Foxnouns.Backend/Controllers/Authentication/FediverseAuthController.cs index edc7b6a..1ae2ef9 100644 --- a/Foxnouns.Backend/Controllers/Authentication/FediverseAuthController.cs +++ b/Foxnouns.Backend/Controllers/Authentication/FediverseAuthController.cs @@ -94,8 +94,7 @@ public class FediverseAuthController( public async Task RegisterAsync([FromBody] OauthRegisterRequest req) { FediverseTicketData? ticketData = await keyCacheService.GetKeyAsync( - $"fediverse:{req.Ticket}", - true + $"fediverse:{req.Ticket}" ); if (ticketData == null) throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket); diff --git a/Foxnouns.Backend/Controllers/DeleteUserController.cs b/Foxnouns.Backend/Controllers/DeleteUserController.cs new file mode 100644 index 0000000..d1c8e62 --- /dev/null +++ b/Foxnouns.Backend/Controllers/DeleteUserController.cs @@ -0,0 +1,89 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Middleware; +using Microsoft.AspNetCore.Mvc; +using NodaTime; + +namespace Foxnouns.Backend.Controllers; + +[Route("/api/internal/self-delete")] +[Authorize("*")] +[ApiExplorerSettings(IgnoreApi = true)] +public class DeleteUserController(DatabaseContext db, IClock clock, ILogger logger) + : ApiControllerBase +{ + private readonly ILogger _logger = logger.ForContext(); + + [HttpPost("delete")] + public async Task DeleteSelfAsync() + { + _logger.Information( + "User {UserId} has requested their account to be deleted", + CurrentUser!.Id + ); + + CurrentUser.Deleted = true; + CurrentUser.DeletedAt = clock.GetCurrentInstant(); + + db.Update(CurrentUser); + await db.SaveChangesAsync(); + return NoContent(); + } + + [HttpPost("force")] + [Limit(UsableByDeletedUsers = true)] + public async Task ForceDeleteAsync() + { + if (!CurrentUser!.Deleted) + throw new ApiError.BadRequest("Your account isn't deleted."); + + _logger.Information( + "User {UserId} has requested an early full delete of their account", + CurrentUser.Id + ); + + // This is the easiest way to force delete a user, don't judge me + CurrentUser.DeletedAt = clock.GetCurrentInstant() - Duration.FromDays(365); + db.Update(CurrentUser); + await db.SaveChangesAsync(); + return NoContent(); + } + + [HttpPost("undelete")] + [Limit(UsableByDeletedUsers = true)] + public async Task UndeleteSelfAsync() + { + if (!CurrentUser!.Deleted) + throw new ApiError.BadRequest("Your account isn't deleted."); + if (CurrentUser!.DeletedBy != null) + { + throw new ApiError.BadRequest( + "Your account has been suspended and can't be reactivated by yourself." + ); + } + + _logger.Information( + "User {UserId} has requested to undelete their account", + CurrentUser.Id + ); + + CurrentUser.Deleted = false; + CurrentUser.DeletedAt = null; + db.Update(CurrentUser); + await db.SaveChangesAsync(); + return NoContent(); + } +} diff --git a/Foxnouns.Backend/Controllers/ExportsController.cs b/Foxnouns.Backend/Controllers/ExportsController.cs index 315efbc..0442386 100644 --- a/Foxnouns.Backend/Controllers/ExportsController.cs +++ b/Foxnouns.Backend/Controllers/ExportsController.cs @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Queuing.Interfaces; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; @@ -26,14 +25,10 @@ namespace Foxnouns.Backend.Controllers; [Route("/api/internal/data-exports")] [Authorize("identify")] +[Limit(UsableByDeletedUsers = true)] [ApiExplorerSettings(IgnoreApi = true)] -public class ExportsController( - ILogger logger, - Config config, - IClock clock, - DatabaseContext db, - IQueue queue -) : ApiControllerBase +public class ExportsController(ILogger logger, Config config, IClock clock, DatabaseContext db) + : ApiControllerBase { private static readonly Duration MinimumTimeBetween = Duration.FromDays(1); private readonly ILogger _logger = logger.ForContext(); @@ -57,7 +52,7 @@ public class ExportsController( } private string ExportUrl(Snowflake userId, string filename) => - $"{config.MediaBaseUrl}/data-exports/{userId}/{filename}.zip"; + $"{config.MediaBaseUrl}/data-exports/{userId}/{filename}/data-export.zip"; [HttpPost] public async Task QueueDataExportAsync() @@ -79,10 +74,7 @@ public class ExportsController( throw new ApiError.BadRequest("You can't request a new data export so soon."); } - queue.QueueInvocableWithPayload( - new CreateDataExportPayload(CurrentUser.Id) - ); - + CreateDataExportJob.Enqueue(CurrentUser.Id); return NoContent(); } } diff --git a/Foxnouns.Backend/Controllers/FlagsController.cs b/Foxnouns.Backend/Controllers/FlagsController.cs index 2b145ac..bed022a 100644 --- a/Foxnouns.Backend/Controllers/FlagsController.cs +++ b/Foxnouns.Backend/Controllers/FlagsController.cs @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Queuing.Interfaces; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; @@ -22,6 +21,7 @@ using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using XidNet; namespace Foxnouns.Backend.Controllers; @@ -29,12 +29,11 @@ namespace Foxnouns.Backend.Controllers; public class FlagsController( DatabaseContext db, UserRendererService userRenderer, - ISnowflakeGenerator snowflakeGenerator, - IQueue queue + ISnowflakeGenerator snowflakeGenerator ) : ApiControllerBase { [HttpGet] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] [Authorize("user.read_flags")] [ProducesResponseType>(statusCode: StatusCodes.Status200OK)] public async Task GetFlagsAsync(CancellationToken ct = default) @@ -64,6 +63,7 @@ public class FlagsController( var flag = new PrideFlag { Id = snowflakeGenerator.GenerateSnowflake(), + LegacyId = Xid.NewXid().ToString(), UserId = CurrentUser!.Id, Name = req.Name, Description = req.Description, @@ -72,10 +72,7 @@ public class FlagsController( db.Add(flag); await db.SaveChangesAsync(); - queue.QueueInvocableWithPayload( - new CreateFlagPayload(flag.Id, CurrentUser!.Id, req.Image) - ); - + CreateFlagJob.Enqueue(new CreateFlagPayload(flag.Id, CurrentUser!.Id, req.Image)); return Accepted(userRenderer.RenderPrideFlag(flag)); } diff --git a/Foxnouns.Backend/Controllers/InternalController.cs b/Foxnouns.Backend/Controllers/InternalController.cs index 85bc774..3954547 100644 --- a/Foxnouns.Backend/Controllers/InternalController.cs +++ b/Foxnouns.Backend/Controllers/InternalController.cs @@ -38,6 +38,8 @@ public partial class InternalController(DatabaseContext db) : ControllerBase { if (template.StartsWith("api/v2")) template = template["api/v2".Length..]; + else if (template.StartsWith("api/v1")) + template = template["api/v1".Length..]; template = PathVarRegex() .Replace(template, "{id}") // Replace all path variables (almost always IDs) with `{id}` .Replace("@me", "{id}"); // Also replace hardcoded `@me` with `{id}` diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index 9b94b30..635dab9 100644 --- a/Foxnouns.Backend/Controllers/MembersController.cs +++ b/Foxnouns.Backend/Controllers/MembersController.cs @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Queuing.Interfaces; using EntityFramework.Exceptions.Common; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; @@ -26,6 +25,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using NodaTime; +using XidNet; namespace Foxnouns.Backend.Controllers; @@ -36,15 +36,16 @@ public class MembersController( MemberRendererService memberRenderer, ISnowflakeGenerator snowflakeGenerator, ObjectStorageService objectStorageService, - IQueue queue, - IClock clock + IClock clock, + ValidationService validationService, + Config config ) : ApiControllerBase { private readonly ILogger _logger = logger.ForContext(); [HttpGet] [ProducesResponseType>(StatusCodes.Status200OK)] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] public async Task GetMembersAsync(string userRef, CancellationToken ct = default) { User user = await db.ResolveUserAsync(userRef, CurrentToken, ct); @@ -53,7 +54,7 @@ public class MembersController( [HttpGet("{memberRef}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] public async Task GetMemberAsync( string userRef, string memberRef, @@ -64,8 +65,6 @@ public class MembersController( return Ok(memberRenderer.RenderMember(member, CurrentToken)); } - public const int MaxMemberCount = 500; - [HttpPost("/api/v2/users/@me/members")] [ProducesResponseType(StatusCodes.Status200OK)] [Authorize("member.create")] @@ -76,31 +75,32 @@ public class MembersController( { ValidationUtils.Validate( [ - ("name", ValidationUtils.ValidateMemberName(req.Name)), - ("display_name", ValidationUtils.ValidateDisplayName(req.DisplayName)), - ("bio", ValidationUtils.ValidateBio(req.Bio)), - ("avatar", ValidationUtils.ValidateAvatar(req.Avatar)), - .. ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences), - .. ValidationUtils.ValidateFieldEntries( + ("name", validationService.ValidateMemberName(req.Name)), + ("display_name", validationService.ValidateDisplayName(req.DisplayName)), + ("bio", validationService.ValidateBio(req.Bio)), + ("avatar", validationService.ValidateAvatar(req.Avatar)), + .. validationService.ValidateFields(req.Fields, CurrentUser!.CustomPreferences), + .. validationService.ValidateFieldEntries( req.Names?.ToArray(), CurrentUser!.CustomPreferences, "names" ), - .. ValidationUtils.ValidatePronouns( + .. validationService.ValidatePronouns( req.Pronouns?.ToArray(), CurrentUser!.CustomPreferences ), - .. ValidationUtils.ValidateLinks(req.Links), + .. validationService.ValidateLinks(req.Links), ] ); int memberCount = await db.Members.CountAsync(m => m.UserId == CurrentUser.Id, ct); - if (memberCount >= MaxMemberCount) + if (memberCount >= config.Limits.MaxMemberCount) throw new ApiError.BadRequest("Maximum number of members reached"); var member = new Member { Id = snowflakeGenerator.GenerateSnowflake(), + LegacyId = Xid.NewXid().ToString(), User = CurrentUser!, Name = req.Name, DisplayName = req.DisplayName, @@ -121,6 +121,9 @@ public class MembersController( CurrentUser!.Id ); + CurrentUser.LastActive = clock.GetCurrentInstant(); + db.Update(CurrentUser); + try { await db.SaveChangesAsync(ct); @@ -137,9 +140,7 @@ public class MembersController( if (req.Avatar != null) { - queue.QueueInvocableWithPayload( - new AvatarUpdatePayload(member.Id, req.Avatar) - ); + MemberAvatarUpdateJob.Enqueue(new AvatarUpdatePayload(member.Id, req.Avatar)); } return Ok(memberRenderer.RenderMember(member, CurrentToken)); @@ -161,25 +162,25 @@ public class MembersController( // These should only take effect when a member's name is changed, not on other changes. if (req.Name != null && req.Name != member.Name) { - errors.Add(("name", ValidationUtils.ValidateMemberName(req.Name))); + errors.Add(("name", validationService.ValidateMemberName(req.Name))); member.Name = req.Name; } if (req.HasProperty(nameof(req.DisplayName))) { - errors.Add(("display_name", ValidationUtils.ValidateDisplayName(req.DisplayName))); + errors.Add(("display_name", validationService.ValidateDisplayName(req.DisplayName))); member.DisplayName = req.DisplayName; } if (req.HasProperty(nameof(req.Bio))) { - errors.Add(("bio", ValidationUtils.ValidateBio(req.Bio))); + errors.Add(("bio", validationService.ValidateBio(req.Bio))); member.Bio = req.Bio; } if (req.HasProperty(nameof(req.Links))) { - errors.AddRange(ValidationUtils.ValidateLinks(req.Links)); + errors.AddRange(validationService.ValidateLinks(req.Links)); member.Links = req.Links ?? []; } @@ -189,7 +190,7 @@ public class MembersController( if (req.Names != null) { errors.AddRange( - ValidationUtils.ValidateFieldEntries( + validationService.ValidateFieldEntries( req.Names, CurrentUser!.CustomPreferences, "names" @@ -201,7 +202,7 @@ public class MembersController( if (req.Pronouns != null) { errors.AddRange( - ValidationUtils.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences) + validationService.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences) ); member.Pronouns = req.Pronouns.ToList(); } @@ -209,7 +210,10 @@ public class MembersController( if (req.Fields != null) { errors.AddRange( - ValidationUtils.ValidateFields(req.Fields.ToList(), CurrentUser!.CustomPreferences) + validationService.ValidateFields( + req.Fields.ToList(), + CurrentUser!.CustomPreferences + ) ); member.Fields = req.Fields.ToList(); } @@ -226,7 +230,7 @@ public class MembersController( } if (req.HasProperty(nameof(req.Avatar))) - errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar))); + errors.Add(("avatar", validationService.ValidateAvatar(req.Avatar))); ValidationUtils.Validate(errors); // This is fired off regardless of whether the transaction is committed @@ -234,11 +238,12 @@ public class MembersController( // so it's in a separate block to the validation above. if (req.HasProperty(nameof(req.Avatar))) { - queue.QueueInvocableWithPayload( - new AvatarUpdatePayload(member.Id, req.Avatar) - ); + MemberAvatarUpdateJob.Enqueue(new AvatarUpdatePayload(member.Id, req.Avatar)); } + CurrentUser.LastActive = clock.GetCurrentInstant(); + db.Update(CurrentUser); + try { await db.SaveChangesAsync(); diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs index 8552164..0166e86 100644 --- a/Foxnouns.Backend/Controllers/MetaController.cs +++ b/Foxnouns.Backend/Controllers/MetaController.cs @@ -12,20 +12,24 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Text.RegularExpressions; +using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; +using Foxnouns.Backend.Services.Caching; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; namespace Foxnouns.Backend.Controllers; [Route("/api/v2/meta")] -public class MetaController : ApiControllerBase +public partial class MetaController(Config config, NoticeCacheService noticeCache) + : ApiControllerBase { private const string Repository = "https://codeberg.org/pronounscc/pronouns.cc"; [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult GetMeta() => + public async Task GetMeta(CancellationToken ct = default) => Ok( new MetaResponse( Repository, @@ -39,16 +43,43 @@ public class MetaController : ApiControllerBase (int)FoxnounsMetrics.UsersActiveDayCount.Value ), new LimitsResponse( - MembersController.MaxMemberCount, - ValidationUtils.MaxBioLength, + config.Limits.MaxMemberCount, + config.Limits.MaxBioLength, ValidationUtils.MaxCustomPreferences, AuthUtils.MaxAuthMethodsPerType, FlagsController.MaxFlagCount - ) + ), + Notice: NoticeResponse(await noticeCache.GetAsync(ct)) ) ); + private static MetaNoticeResponse? NoticeResponse(Notice? notice) => + notice == null ? null : new MetaNoticeResponse(notice.Id, notice.Message); + + [HttpGet("page/{page}")] + public async Task GetStaticPageAsync(string page, CancellationToken ct = default) + { + if (!PageRegex().IsMatch(page)) + { + throw new ApiError.BadRequest("Invalid page name"); + } + + string path = Path.Join(Directory.GetCurrentDirectory(), "static-pages", $"{page}.md"); + try + { + string text = await System.IO.File.ReadAllTextAsync(path, ct); + return Ok(text); + } + catch (FileNotFoundException) + { + throw new ApiError.NotFound("Page not found", code: ErrorCode.PageNotFound); + } + } + [HttpGet("/api/v2/coffee")] public IActionResult BrewCoffee() => - Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot); + StatusCode(StatusCodes.Status418ImATeapot, "Sorry, I'm a teapot!"); + + [GeneratedRegex(@"^[a-z\-_]+$")] + private static partial Regex PageRegex(); } diff --git a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs index 8b556de..304cfa4 100644 --- a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs @@ -30,7 +30,9 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo public async Task GetAuditLogAsync( [FromQuery] AuditLogEntryType? type = null, [FromQuery] int? limit = null, - [FromQuery] Snowflake? before = null + [FromQuery] Snowflake? before = null, + [FromQuery] Snowflake? after = null, + [FromQuery(Name = "by-moderator")] Snowflake? byModerator = null ) { limit = limit switch @@ -41,15 +43,36 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo _ => limit, }; - IQueryable query = db.AuditLog.OrderByDescending(e => e.Id); + IQueryable query = db + .AuditLog.Include(e => e.Report) + .OrderByDescending(e => e.Id); if (before != null) query = query.Where(e => e.Id < before.Value); + else if (after != null) + query = query.Where(e => e.Id > after.Value); + if (type != null) query = query.Where(e => e.Type == type); + if (byModerator != null) + query = query.Where(e => e.ModeratorId == byModerator.Value); List entries = await query.Take(limit!.Value).ToListAsync(); return Ok(entries.Select(moderationRenderer.RenderAuditLogEntry)); } + + [HttpGet("moderators")] + public async Task GetModeratorsAsync(CancellationToken ct = default) + { + var moderators = await db + .Users.Where(u => + !u.Deleted && (u.Role == UserRole.Admin || u.Role == UserRole.Moderator) + ) + .Select(u => new { u.Id, u.Username }) + .OrderBy(u => u.Id) + .ToListAsync(ct); + + return Ok(moderators); + } } diff --git a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs new file mode 100644 index 0000000..9e9fa7f --- /dev/null +++ b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs @@ -0,0 +1,96 @@ +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Dto; +using Foxnouns.Backend.Middleware; +using Foxnouns.Backend.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Foxnouns.Backend.Controllers.Moderation; + +[Route("/api/v2/moderation/lookup")] +[Authorize("user.moderation")] +[Limit(RequireModerator = true)] +public class LookupController( + DatabaseContext db, + UserRendererService userRenderer, + ModerationService moderationService, + ModerationRendererService moderationRenderer +) : ApiControllerBase +{ + [HttpPost] + public async Task QueryUsersAsync( + [FromBody] QueryUsersRequest req, + CancellationToken ct = default + ) + { + var query = db.Users.Select(u => new { u.Id, u.Username }); + query = req.Fuzzy + ? query.Where(u => u.Username.Contains(req.Query)) + : query.Where(u => u.Username == req.Query); + + var users = await query.OrderBy(u => u.Id).Take(100).ToListAsync(ct); + return Ok(users); + } + + [HttpGet("{id}")] + public async Task QueryUserAsync(Snowflake id, CancellationToken ct = default) + { + User user = await db.ResolveUserAsync(id, ct); + + bool showSensitiveData = await moderationService.ShowSensitiveDataAsync( + CurrentUser!, + user, + ct + ); + + List authMethods = showSensitiveData + ? await db + .AuthMethods.Where(a => a.UserId == user.Id) + .Include(a => a.FediverseApplication) + .ToListAsync(ct) + : []; + + return Ok( + new QueryUserResponse( + User: await userRenderer.RenderUserAsync( + user, + renderMembers: false, + renderAuthMethods: false, + ct: ct + ), + MemberListHidden: user.ListHidden, + LastActive: user.LastActive, + LastSidReroll: user.LastSidReroll, + Suspended: user is { Deleted: true, DeletedBy: not null }, + Deleted: user.Deleted, + ShowSensitiveData: showSensitiveData, + AuthMethods: showSensitiveData + ? authMethods.Select(UserRendererService.RenderAuthMethod) + : null + ) + ); + } + + [HttpPost("{id}/sensitive")] + public async Task QuerySensitiveUserDataAsync( + Snowflake id, + [FromBody] QuerySensitiveUserDataRequest req + ) + { + User user = await db.ResolveUserAsync(id); + + // Don't let mods accidentally spam the audit log + bool alreadyAuthorized = await moderationService.ShowSensitiveDataAsync(CurrentUser!, user); + if (alreadyAuthorized) + return NoContent(); + + AuditLogEntry entry = await moderationService.QuerySensitiveDataAsync( + CurrentUser!, + user, + req.Reason + ); + + return Ok(moderationRenderer.RenderAuditLogEntry(entry)); + } +} diff --git a/Foxnouns.Backend/Controllers/Moderation/NoticesController.cs b/Foxnouns.Backend/Controllers/Moderation/NoticesController.cs new file mode 100644 index 0000000..3d2d6bb --- /dev/null +++ b/Foxnouns.Backend/Controllers/Moderation/NoticesController.cs @@ -0,0 +1,77 @@ +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Dto; +using Foxnouns.Backend.Middleware; +using Foxnouns.Backend.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Foxnouns.Backend.Controllers.Moderation; + +[Route("/api/v2/notices")] +[Authorize("user.moderation")] +[Limit(RequireModerator = true)] +public class NoticesController( + DatabaseContext db, + UserRendererService userRenderer, + ISnowflakeGenerator snowflakeGenerator, + IClock clock +) : ApiControllerBase +{ + [HttpGet] + public async Task GetNoticesAsync(CancellationToken ct = default) + { + List notices = await db + .Notices.Include(n => n.Author) + .OrderByDescending(n => n.Id) + .ToListAsync(ct); + return Ok(notices.Select(RenderNotice)); + } + + [HttpPost] + public async Task CreateNoticeAsync(CreateNoticeRequest req) + { + Instant now = clock.GetCurrentInstant(); + if (req.StartTime < now) + { + throw new ApiError.BadRequest( + "Start time cannot be in the past", + "start_time", + req.StartTime + ); + } + + if (req.EndTime < now) + { + throw new ApiError.BadRequest( + "End time cannot be in the past", + "end_time", + req.EndTime + ); + } + + var notice = new Notice + { + Id = snowflakeGenerator.GenerateSnowflake(), + Message = req.Message, + StartTime = req.StartTime ?? clock.GetCurrentInstant(), + EndTime = req.EndTime, + Author = CurrentUser!, + }; + + db.Add(notice); + await db.SaveChangesAsync(); + + return Ok(RenderNotice(notice)); + } + + private NoticeResponse RenderNotice(Notice notice) => + new( + notice.Id, + notice.Message, + notice.StartTime, + notice.EndTime, + userRenderer.RenderPartialUser(notice.Author) + ); +} diff --git a/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs b/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs index 3e9f905..c5472b3 100644 --- a/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs @@ -18,6 +18,7 @@ using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; using Foxnouns.Backend.Middleware; using Foxnouns.Backend.Services; +using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -49,6 +50,8 @@ public class ReportsController( [FromBody] CreateReportRequest req ) { + ValidationUtils.Validate([("context", ValidationUtils.ValidateReportContext(req.Context))]); + User target = await db.ResolveUserAsync(id); if (target.Id == CurrentUser!.Id) @@ -96,6 +99,7 @@ public class ReportsController( TargetUserId = target.Id, TargetMemberId = null, Reason = req.Reason, + Context = req.Context, TargetType = ReportTargetType.User, TargetSnapshot = snapshot, }; @@ -112,6 +116,8 @@ public class ReportsController( [FromBody] CreateReportRequest req ) { + ValidationUtils.Validate([("context", ValidationUtils.ValidateReportContext(req.Context))]); + Member target = await db.ResolveMemberAsync(id); if (target.User.Id == CurrentUser!.Id) @@ -158,6 +164,7 @@ public class ReportsController( TargetUserId = target.User.Id, TargetMemberId = target.Id, Reason = req.Reason, + Context = req.Context, TargetType = ReportTargetType.Member, TargetSnapshot = snapshot, }; @@ -213,7 +220,40 @@ public class ReportsController( return Ok(reports.Select(moderationRenderer.RenderReport)); } + [HttpGet("reports/{id}")] + [Authorize("user.moderation")] + [Limit(RequireModerator = true)] + public async Task GetReportAsync(Snowflake id, CancellationToken ct = default) + { + Report? report = await db + .Reports.Include(r => r.Reporter) + .Include(r => r.TargetUser) + .Include(r => r.TargetMember) + .Include(r => r.AuditLogEntry) + .FirstOrDefaultAsync(r => r.Id == id, ct); + if (report == null) + throw new ApiError.NotFound("No report with that ID found."); + + return Ok( + new ReportDetailResponse( + Report: moderationRenderer.RenderReport(report), + User: await userRenderer.RenderUserAsync( + report.TargetUser, + renderMembers: false, + ct: ct + ), + Member: report.TargetMember != null + ? memberRenderer.RenderMember(report.TargetMember) + : null, + AuditLogEntry: report.AuditLogEntry != null + ? moderationRenderer.RenderAuditLogEntry(report.AuditLogEntry) + : null + ) + ); + } + [HttpPost("reports/{id}/ignore")] + [Authorize("user.moderation")] [Limit(RequireModerator = true)] public async Task IgnoreReportAsync( Snowflake id, diff --git a/Foxnouns.Backend/Controllers/NotificationsController.cs b/Foxnouns.Backend/Controllers/NotificationsController.cs index 8bea907..873344c 100644 --- a/Foxnouns.Backend/Controllers/NotificationsController.cs +++ b/Foxnouns.Backend/Controllers/NotificationsController.cs @@ -1,3 +1,17 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Middleware; @@ -17,7 +31,7 @@ public class NotificationsController( { [HttpGet] [Authorize("user.moderation")] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] public async Task GetNotificationsAsync([FromQuery] bool all = false) { IQueryable query = db.Notifications.Where(n => n.TargetId == CurrentUser!.Id); @@ -31,7 +45,7 @@ public class NotificationsController( [HttpPut("{id}/ack")] [Authorize("user.moderation")] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] public async Task AcknowledgeNotificationAsync(Snowflake id) { Notification? notification = await db.Notifications.FirstOrDefaultAsync(n => diff --git a/Foxnouns.Backend/Controllers/UsersController.cs b/Foxnouns.Backend/Controllers/UsersController.cs index d567bdb..ed9a48f 100644 --- a/Foxnouns.Backend/Controllers/UsersController.cs +++ b/Foxnouns.Backend/Controllers/UsersController.cs @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Queuing.Interfaces; using EntityFramework.Exceptions.Common; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; @@ -34,20 +33,28 @@ public class UsersController( ILogger logger, UserRendererService userRenderer, ISnowflakeGenerator snowflakeGenerator, - IQueue queue, - IClock clock + IClock clock, + ValidationService validationService ) : ApiControllerBase { private readonly ILogger _logger = logger.ForContext(); [HttpGet("{userRef}")] [ProducesResponseType(statusCode: StatusCodes.Status200OK)] - [Limit(UsableBySuspendedUsers = true)] + [Limit(UsableByDeletedUsers = true)] public async Task GetUserAsync(string userRef, CancellationToken ct = default) { User user = await db.ResolveUserAsync(userRef, CurrentToken, ct); return Ok( - await userRenderer.RenderUserAsync(user, CurrentUser, CurrentToken, true, true, ct: ct) + await userRenderer.RenderUserAsync( + user, + CurrentUser, + CurrentToken, + renderMembers: true, + renderAuthMethods: true, + renderSettings: true, + ct: ct + ) ); } @@ -65,32 +72,32 @@ public class UsersController( if (req.Username != null && req.Username != user.Username) { - errors.Add(("username", ValidationUtils.ValidateUsername(req.Username))); + errors.Add(("username", validationService.ValidateUsername(req.Username))); user.Username = req.Username; } if (req.HasProperty(nameof(req.DisplayName))) { - errors.Add(("display_name", ValidationUtils.ValidateDisplayName(req.DisplayName))); + errors.Add(("display_name", validationService.ValidateDisplayName(req.DisplayName))); user.DisplayName = req.DisplayName; } if (req.HasProperty(nameof(req.Bio))) { - errors.Add(("bio", ValidationUtils.ValidateBio(req.Bio))); + errors.Add(("bio", validationService.ValidateBio(req.Bio))); user.Bio = req.Bio; } if (req.HasProperty(nameof(req.Links))) { - errors.AddRange(ValidationUtils.ValidateLinks(req.Links)); + errors.AddRange(validationService.ValidateLinks(req.Links)); user.Links = req.Links ?? []; } if (req.Names != null) { errors.AddRange( - ValidationUtils.ValidateFieldEntries( + validationService.ValidateFieldEntries( req.Names, CurrentUser!.CustomPreferences, "names" @@ -102,7 +109,7 @@ public class UsersController( if (req.Pronouns != null) { errors.AddRange( - ValidationUtils.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences) + validationService.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences) ); user.Pronouns = req.Pronouns.ToList(); } @@ -110,7 +117,10 @@ public class UsersController( if (req.Fields != null) { errors.AddRange( - ValidationUtils.ValidateFields(req.Fields.ToList(), CurrentUser!.CustomPreferences) + validationService.ValidateFields( + req.Fields.ToList(), + CurrentUser!.CustomPreferences + ) ); user.Fields = req.Fields.ToList(); } @@ -123,7 +133,7 @@ public class UsersController( } if (req.HasProperty(nameof(req.Avatar))) - errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar))); + errors.Add(("avatar", validationService.ValidateAvatar(req.Avatar))); if (req.HasProperty(nameof(req.MemberTitle))) { @@ -133,7 +143,9 @@ public class UsersController( } else { - errors.Add(("member_title", ValidationUtils.ValidateDisplayName(req.MemberTitle))); + errors.Add( + ("member_title", validationService.ValidateDisplayName(req.MemberTitle)) + ); user.MemberTitle = req.MemberTitle; } } @@ -171,11 +183,11 @@ public class UsersController( // so it's in a separate block to the validation above. if (req.HasProperty(nameof(req.Avatar))) { - queue.QueueInvocableWithPayload( - new AvatarUpdatePayload(CurrentUser!.Id, req.Avatar) - ); + UserAvatarUpdateJob.Enqueue(new AvatarUpdatePayload(CurrentUser!.Id, req.Avatar)); } + user.LastActive = clock.GetCurrentInstant(); + try { await db.SaveChangesAsync(ct); @@ -222,7 +234,7 @@ public class UsersController( .CustomPreferences.Where(x => req.Any(r => r.Id == x.Key)) .ToDictionary(); - foreach (CustomPreferenceUpdateRequest? r in req) + foreach (CustomPreferenceUpdateRequest r in req) { if (r.Id != null && preferences.ContainsKey(r.Id.Value)) { @@ -233,6 +245,7 @@ public class UsersController( Muted = r.Muted, Size = r.Size, Tooltip = r.Tooltip, + LegacyId = preferences[r.Id.Value].LegacyId, }; } else @@ -244,25 +257,18 @@ public class UsersController( Muted = r.Muted, Size = r.Size, Tooltip = r.Tooltip, + LegacyId = Guid.NewGuid(), }; } } user.CustomPreferences = preferences; + user.LastActive = clock.GetCurrentInstant(); await db.SaveChangesAsync(ct); return Ok(user.CustomPreferences); } - [HttpGet("@me/settings")] - [Authorize("user.read_hidden")] - [ProducesResponseType(statusCode: StatusCodes.Status200OK)] - public async Task GetUserSettingsAsync(CancellationToken ct = default) - { - User user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct); - return Ok(user.Settings); - } - [HttpPatch("@me/settings")] [Authorize("user.read_hidden", "user.update")] [ProducesResponseType(statusCode: StatusCodes.Status200OK)] @@ -275,7 +281,10 @@ public class UsersController( if (req.HasProperty(nameof(req.DarkMode))) user.Settings.DarkMode = req.DarkMode; + if (req.HasProperty(nameof(req.LastReadNotice))) + user.Settings.LastReadNotice = req.LastReadNotice; + user.LastActive = clock.GetCurrentInstant(); db.Update(user); await db.SaveChangesAsync(ct); diff --git a/Foxnouns.Backend/Controllers/V1/V1ReadController.cs b/Foxnouns.Backend/Controllers/V1/V1ReadController.cs new file mode 100644 index 0000000..327f03e --- /dev/null +++ b/Foxnouns.Backend/Controllers/V1/V1ReadController.cs @@ -0,0 +1,120 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Dto.V1; +using Foxnouns.Backend.Middleware; +using Foxnouns.Backend.Services.V1; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Foxnouns.Backend.Controllers.V1; + +[Route("/api/v1")] +public class V1ReadController( + UsersV1Service usersV1Service, + MembersV1Service membersV1Service, + DatabaseContext db +) : ApiControllerBase +{ + [HttpGet("users/@me")] + [Authorize("identify")] + public async Task GetMeAsync(CancellationToken ct = default) + { + User user = await usersV1Service.ResolveUserAsync("@me", CurrentToken, ct); + return Ok(await usersV1Service.RenderCurrentUserAsync(user, ct)); + } + + [HttpGet("users/{userRef}")] + public async Task GetUserAsync(string userRef, CancellationToken ct = default) + { + User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct); + return Ok( + await usersV1Service.RenderUserAsync( + user, + CurrentToken, + renderMembers: true, + renderFlags: true, + ct: ct + ) + ); + } + + [HttpGet("members/{id}")] + public async Task GetMemberAsync(string id, CancellationToken ct = default) + { + Member member = await membersV1Service.ResolveMemberAsync(id, ct); + return Ok( + await membersV1Service.RenderMemberAsync( + member, + CurrentToken, + renderFlags: true, + ct: ct + ) + ); + } + + [HttpGet("users/{userRef}/members")] + public async Task GetUserMembersAsync( + string userRef, + CancellationToken ct = default + ) + { + User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct); + List members = await db + .Members.Where(m => m.UserId == user.Id) + .OrderBy(m => m.Name) + .ToListAsync(ct); + + List responses = []; + foreach (Member member in members) + { + responses.Add( + await membersV1Service.RenderMemberAsync( + member, + CurrentToken, + user, + renderFlags: true, + ct: ct + ) + ); + } + + return Ok(responses); + } + + [HttpGet("users/{userRef}/members/{memberRef}")] + public async Task GetUserMemberAsync( + string userRef, + string memberRef, + CancellationToken ct = default + ) + { + Member member = await membersV1Service.ResolveMemberAsync( + userRef, + memberRef, + CurrentToken, + ct + ); + return Ok( + await membersV1Service.RenderMemberAsync( + member, + CurrentToken, + renderFlags: true, + ct: ct + ) + ); + } +} diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index 9baa143..2bbcbc7 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -64,7 +64,6 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) public DbSet FediverseApplications { get; init; } = null!; public DbSet Tokens { get; init; } = null!; public DbSet Applications { get; init; } = null!; - public DbSet TemporaryKeys { get; init; } = null!; public DbSet DataExports { get; init; } = null!; public DbSet PrideFlags { get; init; } = null!; @@ -74,6 +73,7 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) public DbSet Reports { get; init; } = null!; public DbSet AuditLog { get; init; } = null!; public DbSet Notifications { get; init; } = null!; + public DbSet Notices { get; init; } = null!; protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { @@ -87,7 +87,6 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) modelBuilder.Entity().HasIndex(u => u.Sid).IsUnique(); modelBuilder.Entity().HasIndex(m => new { m.UserId, m.Name }).IsUnique(); modelBuilder.Entity().HasIndex(m => m.Sid).IsUnique(); - modelBuilder.Entity().HasIndex(k => k.Key).IsUnique(); modelBuilder.Entity().HasIndex(d => d.Filename).IsUnique(); // Two indexes on auth_methods, one for fediverse auth and one for all other types. @@ -108,6 +107,12 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) .HasFilter("fediverse_application_id IS NULL") .IsUnique(); + modelBuilder + .Entity() + .HasOne(e => e.Report) + .WithOne(e => e.AuditLogEntry) + .OnDelete(DeleteBehavior.SetNull); + modelBuilder.Entity().Property(u => u.Sid).HasDefaultValueSql("find_free_user_sid()"); modelBuilder.Entity().Property(u => u.Fields).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.Names).HasColumnType("jsonb"); @@ -133,6 +138,26 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) modelBuilder .HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeMemberSid))!) .HasName("find_free_member_sid"); + + // Indexes for legacy IDs for APIv1 + modelBuilder.Entity().HasIndex(u => u.LegacyId).IsUnique(); + modelBuilder.Entity().HasIndex(m => m.LegacyId).IsUnique(); + modelBuilder.Entity().HasIndex(f => f.LegacyId).IsUnique(); + + // a UUID is not an xid, but this should always be set by the application anyway. + // we're just setting it here to shut EFCore up because squashing migrations is for nerds + modelBuilder + .Entity() + .Property(u => u.LegacyId) + .HasDefaultValueSql("gen_random_uuid()"); + modelBuilder + .Entity() + .Property(m => m.LegacyId) + .HasDefaultValueSql("gen_random_uuid()"); + modelBuilder + .Entity() + .Property(f => f.LegacyId) + .HasDefaultValueSql("gen_random_uuid()"); } /// diff --git a/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs b/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs new file mode 100644 index 0000000..3dc6029 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20241218195457_AddContextToReports")] + public partial class AddContextToReports : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "context", + table: "reports", + type: "text", + nullable: true + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "context", table: "reports"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs b/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs new file mode 100644 index 0000000..53a1f72 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20241218201855_MakeAuditLogReportsNullable")] + public partial class MakeAuditLogReportsNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log" + ); + + migrationBuilder.DropIndex(name: "ix_audit_log_report_id", table: "audit_log"); + + migrationBuilder.CreateIndex( + name: "ix_audit_log_report_id", + table: "audit_log", + column: "report_id", + unique: true + ); + + migrationBuilder.AddForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log", + column: "report_id", + principalTable: "reports", + principalColumn: "id", + onDelete: ReferentialAction.SetNull + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log" + ); + + migrationBuilder.DropIndex(name: "ix_audit_log_report_id", table: "audit_log"); + + migrationBuilder.CreateIndex( + name: "ix_audit_log_report_id", + table: "audit_log", + column: "report_id" + ); + + migrationBuilder.AddForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log", + column: "report_id", + principalTable: "reports", + principalColumn: "id" + ); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20241225155818_AddLegacyIds.cs b/Foxnouns.Backend/Database/Migrations/20241225155818_AddLegacyIds.cs new file mode 100644 index 0000000..b8330cb --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20241225155818_AddLegacyIds.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20241225155818_AddLegacyIds")] + public partial class AddLegacyIds : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "legacy_id", + table: "users", + type: "text", + nullable: false, + defaultValueSql: "gen_random_uuid()" + ); + + migrationBuilder.AddColumn( + name: "legacy_id", + table: "pride_flags", + type: "text", + nullable: false, + defaultValueSql: "gen_random_uuid()" + ); + + migrationBuilder.AddColumn( + name: "legacy_id", + table: "members", + type: "text", + nullable: false, + defaultValueSql: "gen_random_uuid()" + ); + + migrationBuilder.CreateIndex( + name: "ix_users_legacy_id", + table: "users", + column: "legacy_id", + unique: true + ); + + migrationBuilder.CreateIndex( + name: "ix_pride_flags_legacy_id", + table: "pride_flags", + column: "legacy_id", + unique: true + ); + + migrationBuilder.CreateIndex( + name: "ix_members_legacy_id", + table: "members", + column: "legacy_id", + unique: true + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex(name: "ix_users_legacy_id", table: "users"); + + migrationBuilder.DropIndex(name: "ix_pride_flags_legacy_id", table: "pride_flags"); + + migrationBuilder.DropIndex(name: "ix_members_legacy_id", table: "members"); + + migrationBuilder.DropColumn(name: "legacy_id", table: "users"); + + migrationBuilder.DropColumn(name: "legacy_id", table: "pride_flags"); + + migrationBuilder.DropColumn(name: "legacy_id", table: "members"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20250304155708_RemoveTemporaryKeys.cs b/Foxnouns.Backend/Database/Migrations/20250304155708_RemoveTemporaryKeys.cs new file mode 100644 index 0000000..27a8ada --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20250304155708_RemoveTemporaryKeys.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20250304155708_RemoveTemporaryKeys")] + public partial class RemoveTemporaryKeys : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "temporary_keys"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "temporary_keys", + columns: table => new + { + id = table + .Column(type: "bigint", nullable: false) + .Annotation( + "Npgsql:ValueGenerationStrategy", + NpgsqlValueGenerationStrategy.IdentityByDefaultColumn + ), + expires = table.Column( + type: "timestamp with time zone", + nullable: false + ), + key = table.Column(type: "text", nullable: false), + value = table.Column(type: "text", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("pk_temporary_keys", x => x.id); + } + ); + + migrationBuilder.CreateIndex( + name: "ix_temporary_keys_key", + table: "temporary_keys", + column: "key", + unique: true + ); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.Designer.cs b/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.Designer.cs new file mode 100644 index 0000000..d2df141 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.Designer.cs @@ -0,0 +1,915 @@ +// +using System.Collections.Generic; +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20250329131053_AddNotices")] + partial class AddNotices + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.PrimitiveCollection("RedirectUris") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("redirect_uris"); + + b.PrimitiveCollection("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.HasKey("Id") + .HasName("pk_applications"); + + b.ToTable("applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.PrimitiveCollection("ClearedFields") + .HasColumnType("text[]") + .HasColumnName("cleared_fields"); + + b.Property("ModeratorId") + .HasColumnType("bigint") + .HasColumnName("moderator_id"); + + b.Property("ModeratorUsername") + .IsRequired() + .HasColumnType("text") + .HasColumnName("moderator_username"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("ReportId") + .HasColumnType("bigint") + .HasColumnName("report_id"); + + b.Property("TargetMemberId") + .HasColumnType("bigint") + .HasColumnName("target_member_id"); + + b.Property("TargetMemberName") + .HasColumnType("text") + .HasColumnName("target_member_name"); + + b.Property("TargetUserId") + .HasColumnType("bigint") + .HasColumnName("target_user_id"); + + b.Property("TargetUsername") + .HasColumnType("text") + .HasColumnName("target_username"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_audit_log"); + + b.HasIndex("ReportId") + .IsUnique() + .HasDatabaseName("ix_audit_log_report_id"); + + b.ToTable("audit_log", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthType") + .HasColumnType("integer") + .HasColumnName("auth_type"); + + b.Property("FediverseApplicationId") + .HasColumnType("bigint") + .HasColumnName("fediverse_application_id"); + + b.Property("RemoteId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("remote_id"); + + b.Property("RemoteUsername") + .HasColumnType("text") + .HasColumnName("remote_username"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_auth_methods"); + + b.HasIndex("FediverseApplicationId") + .HasDatabaseName("ix_auth_methods_fediverse_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_auth_methods_user_id"); + + b.HasIndex("AuthType", "RemoteId") + .IsUnique() + .HasDatabaseName("ix_auth_methods_auth_type_remote_id") + .HasFilter("fediverse_application_id IS NULL"); + + b.HasIndex("AuthType", "RemoteId", "FediverseApplicationId") + .IsUnique() + .HasDatabaseName("ix_auth_methods_auth_type_remote_id_fediverse_application_id") + .HasFilter("fediverse_application_id IS NOT NULL"); + + b.ToTable("auth_methods", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.DataExport", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("text") + .HasColumnName("filename"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_data_exports"); + + b.HasIndex("Filename") + .IsUnique() + .HasDatabaseName("ix_data_exports_filename"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_data_exports_user_id"); + + b.ToTable("data_exports", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("text") + .HasColumnName("domain"); + + b.Property("ForceRefresh") + .HasColumnType("boolean") + .HasColumnName("force_refresh"); + + b.Property("InstanceType") + .HasColumnType("integer") + .HasColumnName("instance_type"); + + b.HasKey("Id") + .HasName("pk_fediverse_applications"); + + b.ToTable("fediverse_applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.PrimitiveCollection("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_member_sid()"); + + b.Property("Unlisted") + .HasColumnType("boolean") + .HasColumnName("unlisted"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_members"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_members_legacy_id"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_members_sid"); + + b.HasIndex("UserId", "Name") + .IsUnique() + .HasDatabaseName("ix_members_user_id_name"); + + b.ToTable("members", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasColumnName("member_id"); + + b.Property("PrideFlagId") + .HasColumnType("bigint") + .HasColumnName("pride_flag_id"); + + b.HasKey("Id") + .HasName("pk_member_flags"); + + b.HasIndex("MemberId") + .HasDatabaseName("ix_member_flags_member_id"); + + b.HasIndex("PrideFlagId") + .HasDatabaseName("ix_member_flags_pride_flag_id"); + + b.ToTable("member_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("bigint") + .HasColumnName("author_id"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_time"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_time"); + + b.HasKey("Id") + .HasName("pk_notices"); + + b.HasIndex("AuthorId") + .HasDatabaseName("ix_notices_author_id"); + + b.ToTable("notices", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("acknowledged_at"); + + b.Property("LocalizationKey") + .HasColumnType("text") + .HasColumnName("localization_key"); + + b.Property>("LocalizationParams") + .IsRequired() + .HasColumnType("hstore") + .HasColumnName("localization_params"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("TargetId") + .HasColumnType("bigint") + .HasColumnName("target_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.HasIndex("TargetId") + .HasDatabaseName("ix_notifications_target_id"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Hash") + .HasColumnType("text") + .HasColumnName("hash"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_pride_flags"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_pride_flags_legacy_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_pride_flags_user_id"); + + b.ToTable("pride_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Context") + .HasColumnType("text") + .HasColumnName("context"); + + b.Property("Reason") + .HasColumnType("integer") + .HasColumnName("reason"); + + b.Property("ReporterId") + .HasColumnType("bigint") + .HasColumnName("reporter_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("TargetMemberId") + .HasColumnType("bigint") + .HasColumnName("target_member_id"); + + b.Property("TargetSnapshot") + .HasColumnType("text") + .HasColumnName("target_snapshot"); + + b.Property("TargetType") + .HasColumnType("integer") + .HasColumnName("target_type"); + + b.Property("TargetUserId") + .HasColumnType("bigint") + .HasColumnName("target_user_id"); + + b.HasKey("Id") + .HasName("pk_reports"); + + b.HasIndex("ReporterId") + .HasDatabaseName("ix_reports_reporter_id"); + + b.HasIndex("TargetMemberId") + .HasDatabaseName("ix_reports_target_member_id"); + + b.HasIndex("TargetUserId") + .HasDatabaseName("ix_reports_target_user_id"); + + b.ToTable("reports", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ApplicationId") + .HasColumnType("bigint") + .HasColumnName("application_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hash"); + + b.Property("ManuallyExpired") + .HasColumnType("boolean") + .HasColumnName("manually_expired"); + + b.PrimitiveCollection("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tokens"); + + b.HasIndex("ApplicationId") + .HasDatabaseName("ix_tokens_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tokens_user_id"); + + b.ToTable("tokens", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property>("CustomPreferences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("custom_preferences"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasColumnName("deleted_by"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_active"); + + b.Property("LastSidReroll") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_sid_reroll"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.PrimitiveCollection("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("ListHidden") + .HasColumnType("boolean") + .HasColumnName("list_hidden"); + + b.Property("MemberTitle") + .HasColumnType("text") + .HasColumnName("member_title"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_user_sid()"); + + b.Property("Timezone") + .HasColumnType("text") + .HasColumnName("timezone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_users_legacy_id"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_users_sid"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PrideFlagId") + .HasColumnType("bigint") + .HasColumnName("pride_flag_id"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_flags"); + + b.HasIndex("PrideFlagId") + .HasDatabaseName("ix_user_flags_pride_flag_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_user_flags_user_id"); + + b.ToTable("user_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Report", "Report") + .WithOne("AuditLogEntry") + .HasForeignKey("Foxnouns.Backend.Database.Models.AuditLogEntry", "ReportId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_audit_log_reports_report_id"); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") + .WithMany() + .HasForeignKey("FediverseApplicationId") + .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("AuthMethods") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_methods_users_user_id"); + + b.Navigation("FediverseApplication"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.DataExport", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("DataExports") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_data_exports_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("Members") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_members_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Member", null) + .WithMany("ProfileFlags") + .HasForeignKey("MemberId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_member_flags_members_member_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") + .WithMany() + .HasForeignKey("PrideFlagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_member_flags_pride_flags_pride_flag_id"); + + b.Navigation("PrideFlag"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notices_users_author_id"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Target") + .WithMany() + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifications_users_target_id"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", null) + .WithMany("Flags") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_pride_flags_users_user_id"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Reporter") + .WithMany() + .HasForeignKey("ReporterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_reports_users_reporter_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.Member", "TargetMember") + .WithMany() + .HasForeignKey("TargetMemberId") + .HasConstraintName("fk_reports_members_target_member_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_reports_users_target_user_id"); + + b.Navigation("Reporter"); + + b.Navigation("TargetMember"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_applications_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_users_user_id"); + + b.Navigation("Application"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") + .WithMany() + .HasForeignKey("PrideFlagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_flags_pride_flags_pride_flag_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", null) + .WithMany("ProfileFlags") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_flags_users_user_id"); + + b.Navigation("PrideFlag"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Navigation("ProfileFlags"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Navigation("AuditLogEntry"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Navigation("AuthMethods"); + + b.Navigation("DataExports"); + + b.Navigation("Flags"); + + b.Navigation("Members"); + + b.Navigation("ProfileFlags"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.cs b/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.cs new file mode 100644 index 0000000..24c5166 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20250329131053_AddNotices.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + public partial class AddNotices : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "notices", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false), + message = table.Column(type: "text", nullable: false), + start_time = table.Column( + type: "timestamp with time zone", + nullable: false + ), + end_time = table.Column( + type: "timestamp with time zone", + nullable: false + ), + author_id = table.Column(type: "bigint", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("pk_notices", x => x.id); + table.ForeignKey( + name: "fk_notices_users_author_id", + column: x => x.author_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateIndex( + name: "ix_notices_author_id", + table: "notices", + column: "author_id" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "notices"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.Designer.cs b/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.Designer.cs new file mode 100644 index 0000000..cb9377d --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.Designer.cs @@ -0,0 +1,923 @@ +// +using System.Collections.Generic; +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20250410192220_AddAvatarMigrations")] + partial class AddAvatarMigrations + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.PrimitiveCollection("RedirectUris") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("redirect_uris"); + + b.PrimitiveCollection("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.HasKey("Id") + .HasName("pk_applications"); + + b.ToTable("applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.PrimitiveCollection("ClearedFields") + .HasColumnType("text[]") + .HasColumnName("cleared_fields"); + + b.Property("ModeratorId") + .HasColumnType("bigint") + .HasColumnName("moderator_id"); + + b.Property("ModeratorUsername") + .IsRequired() + .HasColumnType("text") + .HasColumnName("moderator_username"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("ReportId") + .HasColumnType("bigint") + .HasColumnName("report_id"); + + b.Property("TargetMemberId") + .HasColumnType("bigint") + .HasColumnName("target_member_id"); + + b.Property("TargetMemberName") + .HasColumnType("text") + .HasColumnName("target_member_name"); + + b.Property("TargetUserId") + .HasColumnType("bigint") + .HasColumnName("target_user_id"); + + b.Property("TargetUsername") + .HasColumnType("text") + .HasColumnName("target_username"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_audit_log"); + + b.HasIndex("ReportId") + .IsUnique() + .HasDatabaseName("ix_audit_log_report_id"); + + b.ToTable("audit_log", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthType") + .HasColumnType("integer") + .HasColumnName("auth_type"); + + b.Property("FediverseApplicationId") + .HasColumnType("bigint") + .HasColumnName("fediverse_application_id"); + + b.Property("RemoteId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("remote_id"); + + b.Property("RemoteUsername") + .HasColumnType("text") + .HasColumnName("remote_username"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_auth_methods"); + + b.HasIndex("FediverseApplicationId") + .HasDatabaseName("ix_auth_methods_fediverse_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_auth_methods_user_id"); + + b.HasIndex("AuthType", "RemoteId") + .IsUnique() + .HasDatabaseName("ix_auth_methods_auth_type_remote_id") + .HasFilter("fediverse_application_id IS NULL"); + + b.HasIndex("AuthType", "RemoteId", "FediverseApplicationId") + .IsUnique() + .HasDatabaseName("ix_auth_methods_auth_type_remote_id_fediverse_application_id") + .HasFilter("fediverse_application_id IS NOT NULL"); + + b.ToTable("auth_methods", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.DataExport", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("text") + .HasColumnName("filename"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_data_exports"); + + b.HasIndex("Filename") + .IsUnique() + .HasDatabaseName("ix_data_exports_filename"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_data_exports_user_id"); + + b.ToTable("data_exports", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("text") + .HasColumnName("client_secret"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("text") + .HasColumnName("domain"); + + b.Property("ForceRefresh") + .HasColumnType("boolean") + .HasColumnName("force_refresh"); + + b.Property("InstanceType") + .HasColumnType("integer") + .HasColumnName("instance_type"); + + b.HasKey("Id") + .HasName("pk_fediverse_applications"); + + b.ToTable("fediverse_applications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("AvatarMigrated") + .HasColumnType("boolean") + .HasColumnName("avatar_migrated"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.PrimitiveCollection("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_member_sid()"); + + b.Property("Unlisted") + .HasColumnType("boolean") + .HasColumnName("unlisted"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_members"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_members_legacy_id"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_members_sid"); + + b.HasIndex("UserId", "Name") + .IsUnique() + .HasDatabaseName("ix_members_user_id_name"); + + b.ToTable("members", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasColumnName("member_id"); + + b.Property("PrideFlagId") + .HasColumnType("bigint") + .HasColumnName("pride_flag_id"); + + b.HasKey("Id") + .HasName("pk_member_flags"); + + b.HasIndex("MemberId") + .HasDatabaseName("ix_member_flags_member_id"); + + b.HasIndex("PrideFlagId") + .HasDatabaseName("ix_member_flags_pride_flag_id"); + + b.ToTable("member_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("bigint") + .HasColumnName("author_id"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_time"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_time"); + + b.HasKey("Id") + .HasName("pk_notices"); + + b.HasIndex("AuthorId") + .HasDatabaseName("ix_notices_author_id"); + + b.ToTable("notices", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AcknowledgedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("acknowledged_at"); + + b.Property("LocalizationKey") + .HasColumnType("text") + .HasColumnName("localization_key"); + + b.Property>("LocalizationParams") + .IsRequired() + .HasColumnType("hstore") + .HasColumnName("localization_params"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("TargetId") + .HasColumnType("bigint") + .HasColumnName("target_id"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_notifications"); + + b.HasIndex("TargetId") + .HasDatabaseName("ix_notifications_target_id"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Hash") + .HasColumnType("text") + .HasColumnName("hash"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_pride_flags"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_pride_flags_legacy_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_pride_flags_user_id"); + + b.ToTable("pride_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Context") + .HasColumnType("text") + .HasColumnName("context"); + + b.Property("Reason") + .HasColumnType("integer") + .HasColumnName("reason"); + + b.Property("ReporterId") + .HasColumnType("bigint") + .HasColumnName("reporter_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("TargetMemberId") + .HasColumnType("bigint") + .HasColumnName("target_member_id"); + + b.Property("TargetSnapshot") + .HasColumnType("text") + .HasColumnName("target_snapshot"); + + b.Property("TargetType") + .HasColumnType("integer") + .HasColumnName("target_type"); + + b.Property("TargetUserId") + .HasColumnType("bigint") + .HasColumnName("target_user_id"); + + b.HasKey("Id") + .HasName("pk_reports"); + + b.HasIndex("ReporterId") + .HasDatabaseName("ix_reports_reporter_id"); + + b.HasIndex("TargetMemberId") + .HasDatabaseName("ix_reports_target_member_id"); + + b.HasIndex("TargetUserId") + .HasDatabaseName("ix_reports_target_user_id"); + + b.ToTable("reports", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("ApplicationId") + .HasColumnType("bigint") + .HasColumnName("application_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hash"); + + b.Property("ManuallyExpired") + .HasColumnType("boolean") + .HasColumnName("manually_expired"); + + b.PrimitiveCollection("Scopes") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("scopes"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tokens"); + + b.HasIndex("ApplicationId") + .HasDatabaseName("ix_tokens_application_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tokens_user_id"); + + b.ToTable("tokens", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("Avatar") + .HasColumnType("text") + .HasColumnName("avatar"); + + b.Property("AvatarMigrated") + .HasColumnType("boolean") + .HasColumnName("avatar_migrated"); + + b.Property("Bio") + .HasColumnType("text") + .HasColumnName("bio"); + + b.Property>("CustomPreferences") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("custom_preferences"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasColumnName("deleted_by"); + + b.Property("DisplayName") + .HasColumnType("text") + .HasColumnName("display_name"); + + b.Property>("Fields") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("fields"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_active"); + + b.Property("LastSidReroll") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_sid_reroll"); + + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.PrimitiveCollection("Links") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("links"); + + b.Property("ListHidden") + .HasColumnType("boolean") + .HasColumnName("list_hidden"); + + b.Property("MemberTitle") + .HasColumnType("text") + .HasColumnName("member_title"); + + b.Property>("Names") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("names"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property>("Pronouns") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("pronouns"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Sid") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("sid") + .HasDefaultValueSql("find_free_user_sid()"); + + b.Property("Timezone") + .HasColumnType("text") + .HasColumnName("timezone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_users_legacy_id"); + + b.HasIndex("Sid") + .IsUnique() + .HasDatabaseName("ix_users_sid"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PrideFlagId") + .HasColumnType("bigint") + .HasColumnName("pride_flag_id"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_flags"); + + b.HasIndex("PrideFlagId") + .HasDatabaseName("ix_user_flags_pride_flag_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_user_flags_user_id"); + + b.ToTable("user_flags", (string)null); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Report", "Report") + .WithOne("AuditLogEntry") + .HasForeignKey("Foxnouns.Backend.Database.Models.AuditLogEntry", "ReportId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_audit_log_reports_report_id"); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") + .WithMany() + .HasForeignKey("FediverseApplicationId") + .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("AuthMethods") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_auth_methods_users_user_id"); + + b.Navigation("FediverseApplication"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.DataExport", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("DataExports") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_data_exports_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany("Members") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_members_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Member", null) + .WithMany("ProfileFlags") + .HasForeignKey("MemberId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_member_flags_members_member_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") + .WithMany() + .HasForeignKey("PrideFlagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_member_flags_pride_flags_pride_flag_id"); + + b.Navigation("PrideFlag"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notices_users_author_id"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Target") + .WithMany() + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifications_users_target_id"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", null) + .WithMany("Flags") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_pride_flags_users_user_id"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Reporter") + .WithMany() + .HasForeignKey("ReporterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_reports_users_reporter_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.Member", "TargetMember") + .WithMany() + .HasForeignKey("TargetMemberId") + .HasConstraintName("fk_reports_members_target_member_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_reports_users_target_user_id"); + + b.Navigation("Reporter"); + + b.Navigation("TargetMember"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_applications_application_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tokens_users_user_id"); + + b.Navigation("Application"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") + .WithMany() + .HasForeignKey("PrideFlagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_flags_pride_flags_pride_flag_id"); + + b.HasOne("Foxnouns.Backend.Database.Models.User", null) + .WithMany("ProfileFlags") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_flags_users_user_id"); + + b.Navigation("PrideFlag"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => + { + b.Navigation("ProfileFlags"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Navigation("AuditLogEntry"); + }); + + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => + { + b.Navigation("AuthMethods"); + + b.Navigation("DataExports"); + + b.Navigation("Flags"); + + b.Navigation("Members"); + + b.Navigation("ProfileFlags"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.cs b/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.cs new file mode 100644 index 0000000..ca88605 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20250410192220_AddAvatarMigrations.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + public partial class AddAvatarMigrations : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "avatar_migrated", + table: "users", + type: "boolean", + nullable: false, + defaultValue: false + ); + + migrationBuilder.AddColumn( + name: "avatar_migrated", + table: "members", + type: "boolean", + nullable: false, + defaultValue: false + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "avatar_migrated", table: "users"); + + migrationBuilder.DropColumn(name: "avatar_migrated", table: "members"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs index 79a0232..92db9f9 100644 --- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace Foxnouns.Backend.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("ProductVersion", "9.0.2") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); @@ -113,6 +113,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasName("pk_audit_log"); b.HasIndex("ReportId") + .IsUnique() .HasDatabaseName("ix_audit_log_report_id"); b.ToTable("audit_log", (string)null); @@ -240,6 +241,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("text") .HasColumnName("avatar"); + b.Property("AvatarMigrated") + .HasColumnType("boolean") + .HasColumnName("avatar_migrated"); + b.Property("Bio") .HasColumnType("text") .HasColumnName("bio"); @@ -253,6 +258,13 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("jsonb") .HasColumnName("fields"); + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + b.PrimitiveCollection("Links") .IsRequired() .HasColumnType("text[]") @@ -291,6 +303,10 @@ namespace Foxnouns.Backend.Database.Migrations b.HasKey("Id") .HasName("pk_members"); + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_members_legacy_id"); + b.HasIndex("Sid") .IsUnique() .HasDatabaseName("ix_members_sid"); @@ -331,6 +347,38 @@ namespace Foxnouns.Backend.Database.Migrations b.ToTable("member_flags", (string)null); }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.Property("Id") + .HasColumnType("bigint") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("bigint") + .HasColumnName("author_id"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_time"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_time"); + + b.HasKey("Id") + .HasName("pk_notices"); + + b.HasIndex("AuthorId") + .HasDatabaseName("ix_notices_author_id"); + + b.ToTable("notices", (string)null); + }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => { b.Property("Id") @@ -385,6 +433,13 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("text") .HasColumnName("hash"); + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + b.Property("Name") .IsRequired() .HasColumnType("text") @@ -397,6 +452,10 @@ namespace Foxnouns.Backend.Database.Migrations b.HasKey("Id") .HasName("pk_pride_flags"); + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_pride_flags_legacy_id"); + b.HasIndex("UserId") .HasDatabaseName("ix_pride_flags_user_id"); @@ -409,6 +468,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("bigint") .HasColumnName("id"); + b.Property("Context") + .HasColumnType("text") + .HasColumnName("context"); + b.Property("Reason") .HasColumnType("integer") .HasColumnName("reason"); @@ -452,39 +515,6 @@ namespace Foxnouns.Backend.Database.Migrations b.ToTable("reports", (string)null); }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Expires") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text") - .HasColumnName("key"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("Id") - .HasName("pk_temporary_keys"); - - b.HasIndex("Key") - .IsUnique() - .HasDatabaseName("ix_temporary_keys_key"); - - b.ToTable("temporary_keys", (string)null); - }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => { b.Property("Id") @@ -539,6 +569,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("text") .HasColumnName("avatar"); + b.Property("AvatarMigrated") + .HasColumnType("boolean") + .HasColumnName("avatar_migrated"); + b.Property("Bio") .HasColumnType("text") .HasColumnName("bio"); @@ -577,6 +611,13 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("last_sid_reroll"); + b.Property("LegacyId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasColumnName("legacy_id") + .HasDefaultValueSql("gen_random_uuid()"); + b.PrimitiveCollection("Links") .IsRequired() .HasColumnType("text[]") @@ -632,6 +673,10 @@ namespace Foxnouns.Backend.Database.Migrations b.HasKey("Id") .HasName("pk_users"); + b.HasIndex("LegacyId") + .IsUnique() + .HasDatabaseName("ix_users_legacy_id"); + b.HasIndex("Sid") .IsUnique() .HasDatabaseName("ix_users_sid"); @@ -675,8 +720,9 @@ namespace Foxnouns.Backend.Database.Migrations modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => { b.HasOne("Foxnouns.Backend.Database.Models.Report", "Report") - .WithMany() - .HasForeignKey("ReportId") + .WithOne("AuditLogEntry") + .HasForeignKey("Foxnouns.Backend.Database.Models.AuditLogEntry", "ReportId") + .OnDelete(DeleteBehavior.SetNull) .HasConstraintName("fk_audit_log_reports_report_id"); b.Navigation("Report"); @@ -744,6 +790,18 @@ namespace Foxnouns.Backend.Database.Migrations b.Navigation("PrideFlag"); }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notice", b => + { + b.HasOne("Foxnouns.Backend.Database.Models.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notices_users_author_id"); + + b.Navigation("Author"); + }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Notification", b => { b.HasOne("Foxnouns.Backend.Database.Models.User", "Target") @@ -839,6 +897,11 @@ namespace Foxnouns.Backend.Database.Migrations b.Navigation("ProfileFlags"); }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Navigation("AuditLogEntry"); + }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => { b.Navigation("AuthMethods"); diff --git a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs index a4983ae..84e1a43 100644 --- a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs +++ b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs @@ -12,6 +12,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.ComponentModel.DataAnnotations.Schema; using Foxnouns.Backend.Utils; using Newtonsoft.Json; @@ -40,4 +41,5 @@ public enum AuditLogEntryType WarnUser, WarnUserAndClearProfile, SuspendUser, + QuerySensitiveUserData, } diff --git a/Foxnouns.Backend/Database/Models/Member.cs b/Foxnouns.Backend/Database/Models/Member.cs index b9793e0..85b39f3 100644 --- a/Foxnouns.Backend/Database/Models/Member.cs +++ b/Foxnouns.Backend/Database/Models/Member.cs @@ -18,6 +18,7 @@ public class Member : BaseModel { public required string Name { get; set; } public string Sid { get; set; } = string.Empty; + public required string LegacyId { get; init; } public string? DisplayName { get; set; } public string? Bio { get; set; } public string? Avatar { get; set; } @@ -28,6 +29,9 @@ public class Member : BaseModel public List Pronouns { get; set; } = []; public List Fields { get; set; } = []; + // Only used by avatar-proxy and avatar-migration. + public bool AvatarMigrated { get; set; } = true; + public List ProfileFlags { get; set; } = []; public Snowflake UserId { get; init; } diff --git a/Foxnouns.Backend/Database/Models/Notice.cs b/Foxnouns.Backend/Database/Models/Notice.cs new file mode 100644 index 0000000..c3e6f0d --- /dev/null +++ b/Foxnouns.Backend/Database/Models/Notice.cs @@ -0,0 +1,13 @@ +using NodaTime; + +namespace Foxnouns.Backend.Database.Models; + +public class Notice : BaseModel +{ + public required string Message { get; set; } + public required Instant StartTime { get; set; } + public required Instant EndTime { get; set; } + + public Snowflake AuthorId { get; init; } + public User Author { get; init; } = null!; +} diff --git a/Foxnouns.Backend/Database/Models/PrideFlag.cs b/Foxnouns.Backend/Database/Models/PrideFlag.cs index f103610..0c04ab5 100644 --- a/Foxnouns.Backend/Database/Models/PrideFlag.cs +++ b/Foxnouns.Backend/Database/Models/PrideFlag.cs @@ -17,6 +17,7 @@ namespace Foxnouns.Backend.Database.Models; public class PrideFlag : BaseModel { public required Snowflake UserId { get; init; } + public required string LegacyId { get; init; } // A null hash means the flag hasn't been processed yet. public string? Hash { get; set; } diff --git a/Foxnouns.Backend/Database/Models/Report.cs b/Foxnouns.Backend/Database/Models/Report.cs index e668f44..47b994f 100644 --- a/Foxnouns.Backend/Database/Models/Report.cs +++ b/Foxnouns.Backend/Database/Models/Report.cs @@ -29,9 +29,12 @@ public class Report : BaseModel public ReportStatus Status { get; set; } public ReportReason Reason { get; init; } + public string? Context { get; init; } public ReportTargetType TargetType { get; init; } public string? TargetSnapshot { get; init; } + + public AuditLogEntry? AuditLogEntry { get; set; } } [JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] diff --git a/Foxnouns.Backend/Database/Models/User.cs b/Foxnouns.Backend/Database/Models/User.cs index 12df0ae..fe97b6c 100644 --- a/Foxnouns.Backend/Database/Models/User.cs +++ b/Foxnouns.Backend/Database/Models/User.cs @@ -25,6 +25,7 @@ public class User : BaseModel { public required string Username { get; set; } public string Sid { get; set; } = string.Empty; + public required string LegacyId { get; init; } public string? DisplayName { get; set; } public string? Bio { get; set; } public string? MemberTitle { get; set; } @@ -56,6 +57,9 @@ public class User : BaseModel public Instant? DeletedAt { get; set; } public Snowflake? DeletedBy { get; set; } + // Only used by avatar-proxy and avatar-migration. + public bool AvatarMigrated { get; set; } = true; + [NotMapped] public bool? SelfDelete => Deleted ? DeletedBy != null : null; @@ -69,6 +73,8 @@ public class User : BaseModel // This type is generally serialized directly, so the converter is applied here. [JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] public PreferenceSize Size { get; set; } + + public Guid LegacyId { get; init; } = Guid.NewGuid(); } public static readonly Duration DeleteAfter = Duration.FromDays(30); @@ -92,4 +98,5 @@ public enum PreferenceSize public class UserSettings { public bool? DarkMode { get; set; } + public Snowflake? LastReadNotice { get; set; } } diff --git a/Foxnouns.Backend/Database/Snowflake.cs b/Foxnouns.Backend/Database/Snowflake.cs index fc2188d..a0f127a 100644 --- a/Foxnouns.Backend/Database/Snowflake.cs +++ b/Foxnouns.Backend/Database/Snowflake.cs @@ -113,24 +113,30 @@ public readonly struct Snowflake(ulong value) : IEquatable ) => writer.WriteStringValue(value.Value.ToString()); } - private class JsonConverter : JsonConverter + private class JsonConverter : JsonConverter { public override void WriteJson( JsonWriter writer, - Snowflake value, + Snowflake? value, JsonSerializer serializer ) { - writer.WriteValue(value.Value.ToString()); + if (value != null) + writer.WriteValue(value.Value.ToString()); + else + writer.WriteNull(); } - public override Snowflake ReadJson( + public override Snowflake? ReadJson( JsonReader reader, Type objectType, - Snowflake existingValue, + Snowflake? existingValue, bool hasExistingValue, JsonSerializer serializer - ) => ulong.Parse((string)reader.Value!); + ) => + reader.TokenType is not (JsonToken.None or JsonToken.Null) + ? ulong.Parse((string)reader.Value!) + : null; } private class TypeConverter : System.ComponentModel.TypeConverter diff --git a/Foxnouns.Backend/Dto/Meta.cs b/Foxnouns.Backend/Dto/Meta.cs index 0ff6e80..168327a 100644 --- a/Foxnouns.Backend/Dto/Meta.cs +++ b/Foxnouns.Backend/Dto/Meta.cs @@ -14,6 +14,8 @@ // along with this program. If not, see . // ReSharper disable NotAccessedPositionalProperty.Global +using Foxnouns.Backend.Database; + namespace Foxnouns.Backend.Dto; public record MetaResponse( @@ -22,9 +24,12 @@ public record MetaResponse( string Hash, int Members, UserInfoResponse Users, - LimitsResponse Limits + LimitsResponse Limits, + MetaNoticeResponse? Notice ); +public record MetaNoticeResponse(Snowflake Id, string Message); + public record UserInfoResponse(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay); public record LimitsResponse( diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index 0de65c7..bcc7e8e 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -16,8 +16,10 @@ // ReSharper disable NotAccessedPositionalProperty.Global using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Utils; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NodaTime; namespace Foxnouns.Backend.Dto; @@ -29,10 +31,19 @@ public record ReportResponse( PartialMember? TargetMember, ReportStatus Status, ReportReason Reason, + string? Context, ReportTargetType TargetType, JObject? Snapshot ); +public record ReportDetailResponse( + ReportResponse Report, + UserResponse User, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] MemberResponse? Member, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + AuditLogResponse? AuditLogEntry +); + public record AuditLogResponse( Snowflake Id, AuditLogEntity Moderator, @@ -40,12 +51,23 @@ public record AuditLogResponse( AuditLogEntity? TargetUser, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] AuditLogEntity? TargetMember, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Snowflake? ReportId, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] PartialReport? Report, AuditLogEntryType Type, string? Reason, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string[]? ClearedFields ); +public record PartialReport( + Snowflake Id, + Snowflake ReporterId, + Snowflake TargetUserId, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + Snowflake? TargetMemberId, + ReportReason Reason, + string? Context, + ReportTargetType TargetType +); + public record NotificationResponse( Snowflake Id, NotificationType Type, @@ -57,19 +79,21 @@ public record NotificationResponse( public record AuditLogEntity(Snowflake Id, string Username); -public record CreateReportRequest(ReportReason Reason); +public record CreateReportRequest(ReportReason Reason, string? Context = null); public record IgnoreReportRequest(string? Reason = null); -public record WarnUserRequest( - string Reason, - FieldsToClear[]? ClearFields = null, - Snowflake? MemberId = null, - Snowflake? ReportId = null -); +public class WarnUserRequest +{ + public required string Reason { get; init; } + public FieldsToClear[]? ClearFields { get; init; } + public Snowflake? MemberId { get; init; } + public Snowflake? ReportId { get; init; } +} public record SuspendUserRequest(string Reason, bool ClearProfile, Snowflake? ReportId = null); +[JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] public enum FieldsToClear { DisplayName, @@ -82,3 +106,29 @@ public enum FieldsToClear Flags, CustomPreferences, } + +public record QueryUsersRequest(string Query, bool Fuzzy); + +public record QueryUserResponse( + UserResponse User, + bool MemberListHidden, + Instant LastActive, + Instant LastSidReroll, + bool Suspended, + bool Deleted, + bool ShowSensitiveData, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + IEnumerable? AuthMethods +); + +public record QuerySensitiveUserDataRequest(string Reason); + +public record NoticeResponse( + Snowflake Id, + string Message, + Instant StartTime, + Instant EndTime, + PartialUser Author +); + +public record CreateNoticeRequest(string Message, Instant? StartTime, Instant EndTime); diff --git a/Foxnouns.Backend/Dto/User.cs b/Foxnouns.Backend/Dto/User.cs index f193811..2ae38f1 100644 --- a/Foxnouns.Backend/Dto/User.cs +++ b/Foxnouns.Backend/Dto/User.cs @@ -36,7 +36,7 @@ public record UserResponse( IEnumerable Names, IEnumerable Pronouns, IEnumerable Fields, - Dictionary CustomPreferences, + Dictionary CustomPreferences, IEnumerable Flags, int? UtcOffset, [property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] UserRole Role, @@ -49,7 +49,16 @@ public record UserResponse( [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastSidReroll, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Timezone, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Suspended, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] UserSettings? Settings +); + +public record CustomPreferenceResponse( + string Icon, + string Tooltip, + bool Muted, + bool Favourite, + [property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] PreferenceSize Size ); public record AuthMethodResponse( @@ -71,6 +80,7 @@ public record PartialUser( public class UpdateUserSettingsRequest : PatchRequest { public bool? DarkMode { get; init; } + public Snowflake? LastReadNotice { get; init; } } public class CustomPreferenceUpdateRequest diff --git a/Foxnouns.Backend/Dto/V1/Member.cs b/Foxnouns.Backend/Dto/V1/Member.cs new file mode 100644 index 0000000..c745187 --- /dev/null +++ b/Foxnouns.Backend/Dto/V1/Member.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// ReSharper disable NotAccessedPositionalProperty.Global +using Foxnouns.Backend.Database; +using Newtonsoft.Json; + +namespace Foxnouns.Backend.Dto.V1; + +public record PartialMember( + string Id, + Snowflake IdNew, + string Sid, + string Name, + string? DisplayName, + string? Bio, + string? Avatar, + string[] Links, + FieldEntry[] Names, + PronounEntry[] Pronouns +); + +public record MemberResponse( + string Id, + Snowflake IdNew, + string Sid, + string Name, + string? DisplayName, + string? Bio, + string? Avatar, + string[] Links, + FieldEntry[] Names, + PronounEntry[] Pronouns, + ProfileField[] Fields, + PrideFlag[] Flags, + PartialUser User, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Unlisted +); + +public record PartialUser( + string Id, + Snowflake IdNew, + string Name, + string? DisplayName, + string? Avatar, + Dictionary CustomPreferences +); diff --git a/Foxnouns.Backend/Dto/V1/User.cs b/Foxnouns.Backend/Dto/V1/User.cs new file mode 100644 index 0000000..e80a355 --- /dev/null +++ b/Foxnouns.Backend/Dto/V1/User.cs @@ -0,0 +1,130 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// ReSharper disable NotAccessedPositionalProperty.Global +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Services.V1; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using NodaTime; + +namespace Foxnouns.Backend.Dto.V1; + +public record UserResponse( + string Id, + Snowflake IdNew, + string Sid, + string Name, + string? DisplayName, + string? Bio, + string? MemberTitle, + string? Avatar, + string[] Links, + FieldEntry[] Names, + PronounEntry[] Pronouns, + ProfileField[] Fields, + PrideFlag[] Flags, + PartialMember[] Members, + int? UtcOffset, + Dictionary CustomPreferences +); + +public record CurrentUserResponse( + string Id, + Snowflake IdNew, + string Sid, + string Name, + string? DisplayName, + string? Bio, + string? MemberTitle, + string? Avatar, + string[] Links, + FieldEntry[] Names, + PronounEntry[] Pronouns, + ProfileField[] Fields, + PrideFlag[] Flags, + PartialMember[] Members, + int? UtcOffset, + Dictionary CustomPreferences, + Instant CreatedAt, + string? Timezone, + bool IsAdmin, + bool ListPrivate, + Instant LastSidReroll, + string? Discord, + string? DiscordUsername, + string? Google, + string? GoogleUsername, + string? Tumblr, + string? TumblrUsername, + string? Fediverse, + string? FediverseUsername, + string? FediverseInstance +); + +public record CustomPreference( + string Icon, + string Tooltip, + [property: JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] + PreferenceSize Size, + bool Muted, + bool Favourite +); + +public record ProfileField(string Name, FieldEntry[] Entries) +{ + public static ProfileField FromField( + Field field, + Dictionary customPreferences + ) => new(field.Name, FieldEntry.FromEntries(field.Entries, customPreferences)); + + public static ProfileField[] FromFields( + IEnumerable fields, + Dictionary customPreferences + ) => fields.Select(f => FromField(f, customPreferences)).ToArray(); +} + +public record FieldEntry(string Value, string Status) +{ + public static FieldEntry[] FromEntries( + IEnumerable entries, + Dictionary customPreferences + ) => + entries + .Select(e => new FieldEntry( + e.Value, + V1Utils.TranslateStatus(e.Status, customPreferences) + )) + .ToArray(); +} + +public record PronounEntry(string Pronouns, string? DisplayText, string Status) +{ + public static PronounEntry[] FromPronouns( + IEnumerable pronouns, + Dictionary customPreferences + ) => + pronouns + .Select(p => new PronounEntry( + p.Value, + p.DisplayText, + V1Utils.TranslateStatus(p.Status, customPreferences) + )) + .ToArray(); +} + +public record PrideFlag(string Id, Snowflake IdNew, string Hash, string Name, string? Description); diff --git a/Foxnouns.Backend/ExpectedError.cs b/Foxnouns.Backend/ExpectedError.cs index 6a704e2..647688b 100644 --- a/Foxnouns.Backend/ExpectedError.cs +++ b/Foxnouns.Backend/ExpectedError.cs @@ -164,6 +164,7 @@ public enum ErrorCode GenericApiError, UserNotFound, MemberNotFound, + PageNotFound, AccountAlreadyLinked, LastAuthMethod, InvalidReportTarget, diff --git a/Foxnouns.Backend/Extensions/ImageObjectExtensions.cs b/Foxnouns.Backend/Extensions/ImageObjectExtensions.cs index db0797c..2d3108b 100644 --- a/Foxnouns.Backend/Extensions/ImageObjectExtensions.cs +++ b/Foxnouns.Backend/Extensions/ImageObjectExtensions.cs @@ -33,24 +33,20 @@ public static class ImageObjectExtensions Snowflake id, string hash, CancellationToken ct = default - ) => - await objectStorageService.RemoveObjectAsync( - MemberAvatarUpdateInvocable.Path(id, hash), - ct - ); + ) => await objectStorageService.RemoveObjectAsync(MemberAvatarUpdateJob.Path(id, hash), ct); public static async Task DeleteUserAvatarAsync( this ObjectStorageService objectStorageService, Snowflake id, string hash, CancellationToken ct = default - ) => await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct); + ) => await objectStorageService.RemoveObjectAsync(UserAvatarUpdateJob.Path(id, hash), ct); public static async Task DeleteFlagAsync( this ObjectStorageService objectStorageService, string hash, CancellationToken ct = default - ) => await objectStorageService.RemoveObjectAsync(CreateFlagInvocable.Path(hash), ct); + ) => await objectStorageService.RemoveObjectAsync(CreateFlagJob.Path(hash), ct); public static async Task<(string Hash, Stream Image)> ConvertBase64UriToImage( string uri, diff --git a/Foxnouns.Backend/Extensions/KeyCacheExtensions.cs b/Foxnouns.Backend/Extensions/KeyCacheExtensions.cs index 615cc3d..a4fb444 100644 --- a/Foxnouns.Backend/Extensions/KeyCacheExtensions.cs +++ b/Foxnouns.Backend/Extensions/KeyCacheExtensions.cs @@ -23,23 +23,19 @@ namespace Foxnouns.Backend.Extensions; public static class KeyCacheExtensions { - public static async Task GenerateAuthStateAsync( - this KeyCacheService keyCacheService, - CancellationToken ct = default - ) + public static async Task GenerateAuthStateAsync(this KeyCacheService keyCacheService) { string state = AuthUtils.RandomToken(); - await keyCacheService.SetKeyAsync($"oauth_state:{state}", "", Duration.FromMinutes(10), ct); + await keyCacheService.SetKeyAsync($"oauth_state:{state}", "", Duration.FromMinutes(10)); return state; } public static async Task ValidateAuthStateAsync( this KeyCacheService keyCacheService, - string state, - CancellationToken ct = default + string state ) { - string? val = await keyCacheService.GetKeyAsync($"oauth_state:{state}", ct: ct); + string? val = await keyCacheService.GetKeyAsync($"oauth_state:{state}"); if (val == null) throw new ApiError.BadRequest("Invalid OAuth state"); } @@ -47,63 +43,55 @@ public static class KeyCacheExtensions public static async Task GenerateRegisterEmailStateAsync( this KeyCacheService keyCacheService, string email, - Snowflake? userId = null, - CancellationToken ct = default + Snowflake? userId = null ) { string state = AuthUtils.RandomToken(); await keyCacheService.SetKeyAsync( $"email_state:{state}", new RegisterEmailState(email, userId), - Duration.FromDays(1), - ct + Duration.FromDays(1) ); return state; } public static async Task GetRegisterEmailStateAsync( this KeyCacheService keyCacheService, - string state, - CancellationToken ct = default - ) => await keyCacheService.GetKeyAsync($"email_state:{state}", ct: ct); + string state + ) => await keyCacheService.GetKeyAsync($"email_state:{state}"); public static async Task GenerateAddExtraAccountStateAsync( this KeyCacheService keyCacheService, AuthType authType, Snowflake userId, - string? instance = null, - CancellationToken ct = default + string? instance = null ) { string state = AuthUtils.RandomToken(); await keyCacheService.SetKeyAsync( $"add_account:{state}", new AddExtraAccountState(authType, userId, instance), - Duration.FromDays(1), - ct + Duration.FromDays(1) ); return state; } public static async Task GetAddExtraAccountStateAsync( this KeyCacheService keyCacheService, - string state, - CancellationToken ct = default - ) => await keyCacheService.GetKeyAsync($"add_account:{state}", true, ct); + string state + ) => await keyCacheService.GetKeyAsync($"add_account:{state}", true); public static async Task GenerateForgotPasswordStateAsync( this KeyCacheService keyCacheService, string email, - Snowflake userId, - CancellationToken ct = default + Snowflake userId ) { string state = AuthUtils.RandomToken(); await keyCacheService.SetKeyAsync( $"forgot_password:{state}", new ForgotPasswordState(email, userId), - Duration.FromHours(1), - ct + Duration.FromHours(1) ); return state; } @@ -111,14 +99,8 @@ public static class KeyCacheExtensions public static async Task GetForgotPasswordStateAsync( this KeyCacheService keyCacheService, string state, - bool delete = true, - CancellationToken ct = default - ) => - await keyCacheService.GetKeyAsync( - $"forgot_password:{state}", - delete, - ct - ); + bool delete = true + ) => await keyCacheService.GetKeyAsync($"forgot_password:{state}", delete); } public record RegisterEmailState( diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs index 64564b2..c3efda6 100644 --- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs +++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs @@ -15,13 +15,18 @@ using Coravel; using Coravel.Queuing.Interfaces; using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Jobs; using Foxnouns.Backend.Middleware; using Foxnouns.Backend.Services; using Foxnouns.Backend.Services.Auth; +using Foxnouns.Backend.Services.Caching; +using Foxnouns.Backend.Services.V1; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Http.Resilience; using Minio; using NodaTime; +using Polly; using Prometheus; using Serilog; using Serilog.Events; @@ -50,9 +55,12 @@ public static class WebApplicationExtensions "Microsoft.EntityFrameworkCore.Database.Command", config.Logging.LogQueries ? LogEventLevel.Information : LogEventLevel.Fatal ) + // These spam the output even on INF level .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) + // Hangfire's debug-level logs are extremely spammy for no reason + .MinimumLevel.Override("Hangfire", LogEventLevel.Information) .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen); if (config.Logging.SeqLogUrl != null) @@ -96,6 +104,40 @@ public static class WebApplicationExtensions builder.Host.ConfigureServices( (ctx, services) => { + // create a single HTTP client for all requests. + // it's also configured with a retry mechanism, so that requests aren't immediately lost to the void if they fail + services.AddSingleton(_ => + { + // ReSharper disable once SuggestVarOrType_Elsewhere + var retryPipeline = new ResiliencePipelineBuilder() + .AddRetry( + new HttpRetryStrategyOptions + { + BackoffType = DelayBackoffType.Linear, + MaxRetryAttempts = 3, + } + ) + .Build(); + + var resilienceHandler = new ResilienceHandler(retryPipeline) + { + InnerHandler = new SocketsHttpHandler + { + PooledConnectionLifetime = TimeSpan.FromMinutes(15), + }, + }; + + var client = new HttpClient(resilienceHandler); + client.DefaultRequestHeaders.Remove("User-Agent"); + client.DefaultRequestHeaders.Remove("Accept"); + client.DefaultRequestHeaders.Add( + "User-Agent", + $"pronouns.cc/{BuildInfo.Version}" + ); + client.DefaultRequestHeaders.Add("Accept", "application/json"); + return client; + }); + services .AddQueue() .AddSmtpMailer(ctx.Configuration) @@ -111,23 +153,28 @@ public static class WebApplicationExtensions .AddSnowflakeGenerator() .AddSingleton() .AddSingleton() + .AddSingleton() .AddScoped() .AddScoped() .AddScoped() .AddScoped() .AddScoped() - .AddScoped() .AddScoped() .AddScoped() .AddScoped() .AddTransient() + .AddTransient() + .AddSingleton() // Background services .AddHostedService() // Transient jobs - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient(); + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + // Legacy services + .AddScoped() + .AddScoped(); if (!config.Logging.EnableMetrics) services.AddHostedService(); @@ -152,9 +199,6 @@ public static class WebApplicationExtensions public static async Task Initialize(this WebApplication app, string[] args) { - // Read version information from .version in the repository root - await BuildInfo.ReadBuildInfo(); - app.Services.ConfigureQueue() .LogQueuedTaskProgress(app.Services.GetRequiredService>()); diff --git a/Foxnouns.Backend/Foxnouns.Backend.csproj b/Foxnouns.Backend/Foxnouns.Backend.csproj index 8238fc8..42a3beb 100644 --- a/Foxnouns.Backend/Foxnouns.Backend.csproj +++ b/Foxnouns.Backend/Foxnouns.Backend.csproj @@ -8,42 +8,48 @@ - - + + + + + - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + - - - - + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - - + + + + + diff --git a/Foxnouns.Backend/Jobs/CreateDataExportInvocable.cs b/Foxnouns.Backend/Jobs/CreateDataExportJob.cs similarity index 91% rename from Foxnouns.Backend/Jobs/CreateDataExportInvocable.cs rename to Foxnouns.Backend/Jobs/CreateDataExportJob.cs index 4d9e1b0..1c392f7 100644 --- a/Foxnouns.Backend/Jobs/CreateDataExportInvocable.cs +++ b/Foxnouns.Backend/Jobs/CreateDataExportJob.cs @@ -14,11 +14,11 @@ // along with this program. If not, see . using System.IO.Compression; using System.Net; -using Coravel.Invocable; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; +using Hangfire; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using NodaTime; @@ -26,7 +26,8 @@ using NodaTime.Text; namespace Foxnouns.Backend.Jobs; -public class CreateDataExportInvocable( +public class CreateDataExportJob( + HttpClient client, DatabaseContext db, IClock clock, UserRendererService userRenderer, @@ -34,37 +35,40 @@ public class CreateDataExportInvocable( ObjectStorageService objectStorageService, ISnowflakeGenerator snowflakeGenerator, ILogger logger -) : IInvocable, IInvocableWithPayload +) { - private static readonly HttpClient Client = new(); - private readonly ILogger _logger = logger.ForContext(); - public required CreateDataExportPayload Payload { get; set; } + private readonly ILogger _logger = logger.ForContext(); - public async Task Invoke() + public static void Enqueue(Snowflake userId) + { + BackgroundJob.Enqueue(j => j.InvokeAsync(userId)); + } + + public async Task InvokeAsync(Snowflake userId) { try { - await InvokeAsync(); + await InvokeAsyncInner(userId); } catch (Exception e) { - _logger.Error(e, "Error generating data export for user {UserId}", Payload.UserId); + _logger.Error(e, "Error generating data export for user {UserId}", userId); } } - private async Task InvokeAsync() + private async Task InvokeAsyncInner(Snowflake userId) { User? user = await db .Users.Include(u => u.AuthMethods) .Include(u => u.Flags) .Include(u => u.ProfileFlags) .AsSplitQuery() - .FirstOrDefaultAsync(u => u.Id == Payload.UserId); + .FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { _logger.Warning( "Received create data export request for user {UserId} but no such user exists, deleted or otherwise. Ignoring request", - Payload.UserId + userId ); return; } @@ -197,7 +201,7 @@ public class CreateDataExportInvocable( if (s3Path == null) return; - HttpResponseMessage resp = await Client.GetAsync(s3Path); + HttpResponseMessage resp = await client.GetAsync(s3Path); if (resp.StatusCode != HttpStatusCode.OK) { _logger.Warning("S3 path {S3Path} returned a non-200 status, not saving file", s3Path); @@ -220,5 +224,5 @@ public class CreateDataExportInvocable( } private static string ExportPath(Snowflake userId, string b64) => - $"data-exports/{userId}/{b64}.zip"; + $"data-exports/{userId}/{b64}/data-export.zip"; } diff --git a/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs b/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs index 1b8905b..e40bfa4 100644 --- a/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs +++ b/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs @@ -12,49 +12,53 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Invocable; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Extensions; using Foxnouns.Backend.Services; +using Hangfire; using Microsoft.EntityFrameworkCore; namespace Foxnouns.Backend.Jobs; -public class CreateFlagInvocable( +public class CreateFlagJob( DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger -) : IInvocable, IInvocableWithPayload +) { - private readonly ILogger _logger = logger.ForContext(); - public required CreateFlagPayload Payload { get; set; } + private readonly ILogger _logger = logger.ForContext(); - public async Task Invoke() + public static void Enqueue(CreateFlagPayload payload) + { + BackgroundJob.Enqueue(j => j.InvokeAsync(payload)); + } + + public async Task InvokeAsync(CreateFlagPayload payload) { _logger.Information( "Creating flag {FlagId} for user {UserId} with image data length {DataLength}", - Payload.Id, - Payload.UserId, - Payload.ImageData.Length + payload.Id, + payload.UserId, + payload.ImageData.Length ); try { PrideFlag? flag = await db.PrideFlags.FirstOrDefaultAsync(f => - f.Id == Payload.Id && f.UserId == Payload.UserId + f.Id == payload.Id && f.UserId == payload.UserId ); if (flag == null) { _logger.Warning( "Got a flag create job for {FlagId} but it doesn't exist, aborting", - Payload.Id + payload.Id ); return; } (string? hash, Stream? image) = await ImageObjectExtensions.ConvertBase64UriToImage( - Payload.ImageData, + payload.ImageData, 256, false ); @@ -68,7 +72,7 @@ public class CreateFlagInvocable( } catch (ArgumentException ae) { - _logger.Warning("Invalid data URI for flag {FlagId}: {Reason}", Payload.Id, ae.Message); + _logger.Warning("Invalid data URI for flag {FlagId}: {Reason}", payload.Id, ae.Message); } throw new NotImplementedException(); diff --git a/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs b/Foxnouns.Backend/Jobs/MemberAvatarUpdateJob.cs similarity index 85% rename from Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs rename to Foxnouns.Backend/Jobs/MemberAvatarUpdateJob.cs index 01ec9e3..c1d2df4 100644 --- a/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs +++ b/Foxnouns.Backend/Jobs/MemberAvatarUpdateJob.cs @@ -12,29 +12,33 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Invocable; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Extensions; using Foxnouns.Backend.Services; +using Hangfire; namespace Foxnouns.Backend.Jobs; -public class MemberAvatarUpdateInvocable( +public class MemberAvatarUpdateJob( DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger -) : IInvocable, IInvocableWithPayload +) { - private readonly ILogger _logger = logger.ForContext(); - public required AvatarUpdatePayload Payload { get; set; } + private readonly ILogger _logger = logger.ForContext(); - public async Task Invoke() + public static void Enqueue(AvatarUpdatePayload payload) { - if (Payload.NewAvatar != null) - await UpdateMemberAvatarAsync(Payload.Id, Payload.NewAvatar); + BackgroundJob.Enqueue(j => j.InvokeAsync(payload)); + } + + public async Task InvokeAsync(AvatarUpdatePayload payload) + { + if (payload.NewAvatar != null) + await UpdateMemberAvatarAsync(payload.Id, payload.NewAvatar); else - await ClearMemberAvatarAsync(Payload.Id); + await ClearMemberAvatarAsync(payload.Id); } private async Task UpdateMemberAvatarAsync(Snowflake id, string newAvatar) @@ -63,6 +67,7 @@ public class MemberAvatarUpdateInvocable( await objectStorageService.PutObjectAsync(Path(id, hash), image, "image/webp"); member.Avatar = hash; + member.AvatarMigrated = true; await db.SaveChangesAsync(); if (prevHash != null && prevHash != hash) diff --git a/Foxnouns.Backend/Jobs/Payloads.cs b/Foxnouns.Backend/Jobs/Payloads.cs index 374a5b7..1f76ea2 100644 --- a/Foxnouns.Backend/Jobs/Payloads.cs +++ b/Foxnouns.Backend/Jobs/Payloads.cs @@ -19,5 +19,3 @@ namespace Foxnouns.Backend.Jobs; public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar); public record CreateFlagPayload(Snowflake Id, Snowflake UserId, string ImageData); - -public record CreateDataExportPayload(Snowflake UserId); diff --git a/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs b/Foxnouns.Backend/Jobs/UserAvatarUpdateJob.cs similarity index 87% rename from Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs rename to Foxnouns.Backend/Jobs/UserAvatarUpdateJob.cs index 862d0da..cf7bed0 100644 --- a/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs +++ b/Foxnouns.Backend/Jobs/UserAvatarUpdateJob.cs @@ -12,29 +12,33 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using Coravel.Invocable; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Extensions; using Foxnouns.Backend.Services; +using Hangfire; namespace Foxnouns.Backend.Jobs; -public class UserAvatarUpdateInvocable( +public class UserAvatarUpdateJob( DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger -) : IInvocable, IInvocableWithPayload +) { - private readonly ILogger _logger = logger.ForContext(); - public required AvatarUpdatePayload Payload { get; set; } + private readonly ILogger _logger = logger.ForContext(); - public async Task Invoke() + public static void Enqueue(AvatarUpdatePayload payload) { - if (Payload.NewAvatar != null) - await UpdateUserAvatarAsync(Payload.Id, Payload.NewAvatar); + BackgroundJob.Enqueue(j => j.InvokeAsync(payload)); + } + + public async Task InvokeAsync(AvatarUpdatePayload payload) + { + if (payload.NewAvatar != null) + await UpdateUserAvatarAsync(payload.Id, payload.NewAvatar); else - await ClearUserAvatarAsync(Payload.Id); + await ClearUserAvatarAsync(payload.Id); } private async Task UpdateUserAvatarAsync(Snowflake id, string newAvatar) @@ -64,6 +68,7 @@ public class UserAvatarUpdateInvocable( await objectStorageService.PutObjectAsync(Path(id, hash), image, "image/webp"); user.Avatar = hash; + user.AvatarMigrated = true; await db.SaveChangesAsync(); if (prevHash != null && prevHash != hash) diff --git a/Foxnouns.Backend/Middleware/LimitMiddleware.cs b/Foxnouns.Backend/Middleware/LimitMiddleware.cs index 1c5f522..6092041 100644 --- a/Foxnouns.Backend/Middleware/LimitMiddleware.cs +++ b/Foxnouns.Backend/Middleware/LimitMiddleware.cs @@ -41,7 +41,7 @@ public class LimitMiddleware : IMiddleware return; } - if (token?.User.Deleted == true && !attribute.UsableBySuspendedUsers) + if (token?.User.Deleted == true && !attribute.UsableByDeletedUsers) throw new ApiError.Forbidden("Deleted users cannot access this endpoint."); if (attribute.RequireAdmin && token?.User.Role != UserRole.Admin) @@ -62,7 +62,7 @@ public class LimitMiddleware : IMiddleware [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class LimitAttribute : Attribute { - public bool UsableBySuspendedUsers { get; init; } + public bool UsableByDeletedUsers { get; init; } public bool RequireAdmin { get; init; } public bool RequireModerator { get; init; } } diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs index 66e57a6..b266248 100644 --- a/Foxnouns.Backend/Program.cs +++ b/Foxnouns.Backend/Program.cs @@ -19,11 +19,12 @@ using Foxnouns.Backend.Extensions; using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; using Foxnouns.Backend.Utils.OpenApi; +using Hangfire; +using Hangfire.Redis.StackExchange; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Prometheus; -using Scalar.AspNetCore; using Sentry.Extensibility; using Serilog; @@ -33,6 +34,9 @@ Config config = builder.AddConfiguration(); builder.AddSerilog(); +// Read version information from .version in the repository root +await BuildInfo.ReadBuildInfo(); + builder .WebHost.UseSentry(opts => { @@ -46,7 +50,8 @@ builder // No valid request body will ever come close to this limit, // but the limit is slightly higher to prevent valid requests from being rejected. opts.Limits.MaxRequestBodySize = 2 * 1024 * 1024; - }); + }) + .UseUrls(config.Address); builder .Services.AddControllers() @@ -63,16 +68,27 @@ builder { NamingStrategy = new SnakeCaseNamingStrategy(), }; + options.SerializerSettings.DateParseHandling = DateParseHandling.None; }) .ConfigureApiBehaviorOptions(options => { - // the type isn't needed but without it, rider keeps complaining for no reason (it compiles just fine) - options.InvalidModelStateResponseFactory = (ActionContext actionContext) => - new BadRequestObjectResult( - new ApiError.AspBadRequest("Bad request", actionContext.ModelState).ToJson() - ); + options.InvalidModelStateResponseFactory = actionContext => new BadRequestObjectResult( + new ApiError.AspBadRequest("Bad request", actionContext.ModelState).ToJson() + ); }); +builder + .Services.AddHangfire( + (services, c) => + { + c.UseRedisStorage( + services.GetRequiredService().Multiplexer, + new RedisStorageOptions { Prefix = "foxnouns_net:" } + ); + } + ) + .AddHangfireServer(); + builder.Services.AddOpenApi( "v2", options => @@ -109,16 +125,19 @@ if (config.Logging.SentryTracing) app.UseCors(); app.UseCustomMiddleware(); app.MapControllers(); -app.MapOpenApi("/api-docs/openapi/{documentName}.json"); -app.MapScalarApiReference(options => -{ - options.Title = "pronouns.cc API"; - options.OpenApiRoutePattern = "/api-docs/openapi/{documentName}.json"; - options.EndpointPathPrefix = "/api-docs/{documentName}"; -}); +app.UseHangfireDashboard(); -app.Urls.Clear(); -app.Urls.Add(config.Address); +// TODO: I can't figure out why this doesn't work yet +// TODO: Manually write API docs in the meantime +// app.MapOpenApi("/api-docs/openapi/{documentName}.json"); +// app.MapScalarApiReference( +// "/api-docs/", +// options => +// { +// options.Title = "pronouns.cc API"; +// options.OpenApiRoutePattern = "/api-docs/openapi/{documentName}.json"; +// } +// ); // Make sure metrics are updated whenever Prometheus scrapes them Metrics.DefaultRegistry.AddBeforeCollectCallback(async ct => diff --git a/Foxnouns.Backend/Services/Auth/AuthService.cs b/Foxnouns.Backend/Services/Auth/AuthService.cs index f8c2428..e3e3edb 100644 --- a/Foxnouns.Backend/Services/Auth/AuthService.cs +++ b/Foxnouns.Backend/Services/Auth/AuthService.cs @@ -20,6 +20,7 @@ using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using NodaTime; +using XidNet; namespace Foxnouns.Backend.Services.Auth; @@ -28,7 +29,8 @@ public class AuthService( ILogger logger, DatabaseContext db, ISnowflakeGenerator snowflakeGenerator, - UserRendererService userRenderer + UserRendererService userRenderer, + ValidationService validationService ) { private readonly ILogger _logger = logger.ForContext(); @@ -48,7 +50,7 @@ public class AuthService( // Validate username and whether it's not taken ValidationUtils.Validate( [ - ("username", ValidationUtils.ValidateUsername(username)), + ("username", validationService.ValidateUsername(username)), ("password", ValidationUtils.ValidatePassword(password)), ] ); @@ -70,6 +72,7 @@ public class AuthService( }, LastActive = clock.GetCurrentInstant(), Sid = null!, + LegacyId = Xid.NewXid().ToString(), }; db.Add(user); @@ -95,7 +98,7 @@ public class AuthService( AssertValidAuthType(authType, instance); // Validate username and whether it's not taken - ValidationUtils.Validate([("username", ValidationUtils.ValidateUsername(username))]); + ValidationUtils.Validate([("username", validationService.ValidateUsername(username))]); if (await db.Users.AnyAsync(u => u.Username == username, ct)) throw new ApiError.BadRequest("Username is already taken", "username", username); @@ -116,6 +119,7 @@ public class AuthService( }, LastActive = clock.GetCurrentInstant(), Sid = null!, + LegacyId = Xid.NewXid().ToString(), }; db.Add(user); @@ -249,14 +253,14 @@ public class AuthService( { AssertValidAuthType(authType, app); - // This is already checked when + // This is already checked when generating an add account state, but we check it here too just in case. int currentCount = await db .AuthMethods.Where(m => m.UserId == userId && m.AuthType == authType) .CountAsync(ct); if (currentCount >= AuthUtils.MaxAuthMethodsPerType) { throw new ApiError.BadRequest( - "Too many linked accounts of this type, maximum of 3 per account." + $"Too many linked accounts of this type, maximum of {AuthUtils.MaxAuthMethodsPerType} per account." ); } diff --git a/Foxnouns.Backend/Services/Auth/FediverseAuthService.Mastodon.cs b/Foxnouns.Backend/Services/Auth/FediverseAuthService.Mastodon.cs index 25d5327..225461c 100644 --- a/Foxnouns.Backend/Services/Auth/FediverseAuthService.Mastodon.cs +++ b/Foxnouns.Backend/Services/Auth/FediverseAuthService.Mastodon.cs @@ -25,20 +25,20 @@ namespace Foxnouns.Backend.Services.Auth; public partial class FediverseAuthService { private string MastodonRedirectUri(string instance) => - $"{_config.BaseUrl}/auth/callback/mastodon/{instance}"; + $"{config.BaseUrl}/auth/callback/mastodon/{instance}"; private async Task CreateMastodonApplicationAsync( string instance, Snowflake? existingAppId = null ) { - HttpResponseMessage resp = await _client.PostAsJsonAsync( + HttpResponseMessage resp = await client.PostAsJsonAsync( $"https://{instance}/api/v1/apps", new CreateMastodonApplicationRequest( - $"pronouns.cc (+{_config.BaseUrl})", + $"pronouns.cc (+{config.BaseUrl})", MastodonRedirectUri(instance), "read read:accounts", - _config.BaseUrl + config.BaseUrl ) ); resp.EnsureSuccessStatusCode(); @@ -58,19 +58,19 @@ public partial class FediverseAuthService { app = new FediverseApplication { - Id = existingAppId ?? _snowflakeGenerator.GenerateSnowflake(), + Id = existingAppId ?? snowflakeGenerator.GenerateSnowflake(), ClientId = mastodonApp.ClientId, ClientSecret = mastodonApp.ClientSecret, Domain = instance, InstanceType = FediverseInstanceType.MastodonApi, }; - _db.Add(app); + db.Add(app); } else { app = - await _db.FediverseApplications.FindAsync(existingAppId) + await db.FediverseApplications.FindAsync(existingAppId) ?? throw new FoxnounsError($"Existing app with ID {existingAppId} was null"); app.ClientId = mastodonApp.ClientId; @@ -78,7 +78,7 @@ public partial class FediverseAuthService app.InstanceType = FediverseInstanceType.MastodonApi; } - await _db.SaveChangesAsync(); + await db.SaveChangesAsync(); return app; } @@ -90,9 +90,9 @@ public partial class FediverseAuthService ) { if (state != null) - await _keyCacheService.ValidateAuthStateAsync(state); + await keyCacheService.ValidateAuthStateAsync(state); - HttpResponseMessage tokenResp = await _client.PostAsync( + HttpResponseMessage tokenResp = await client.PostAsync( MastodonTokenUri(app.Domain), new FormUrlEncodedContent( new Dictionary @@ -123,7 +123,7 @@ public partial class FediverseAuthService var req = new HttpRequestMessage(HttpMethod.Get, MastodonCurrentUserUri(app.Domain)); req.Headers.Add("Authorization", $"Bearer {token}"); - HttpResponseMessage currentUserResp = await _client.SendAsync(req); + HttpResponseMessage currentUserResp = await client.SendAsync(req); currentUserResp.EnsureSuccessStatusCode(); FediverseUser? user = await currentUserResp.Content.ReadFromJsonAsync(); if (user == null) @@ -151,7 +151,7 @@ public partial class FediverseAuthService app = await CreateMastodonApplicationAsync(app.Domain, app.Id); } - state ??= HttpUtility.UrlEncode(await _keyCacheService.GenerateAuthStateAsync()); + state ??= HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync()); return $"https://{app.Domain}/oauth/authorize?response_type=code" + $"&client_id={app.ClientId}" diff --git a/Foxnouns.Backend/Services/Auth/FediverseAuthService.Misskey.cs b/Foxnouns.Backend/Services/Auth/FediverseAuthService.Misskey.cs index 10a61e4..bf6c4b4 100644 --- a/Foxnouns.Backend/Services/Auth/FediverseAuthService.Misskey.cs +++ b/Foxnouns.Backend/Services/Auth/FediverseAuthService.Misskey.cs @@ -34,11 +34,11 @@ public partial class FediverseAuthService Snowflake? existingAppId = null ) { - HttpResponseMessage resp = await _client.PostAsJsonAsync( + HttpResponseMessage resp = await client.PostAsJsonAsync( MisskeyAppUri(instance), new CreateMisskeyApplicationRequest( - $"pronouns.cc (+{_config.BaseUrl})", - $"pronouns.cc on {_config.BaseUrl}", + $"pronouns.cc (+{config.BaseUrl})", + $"pronouns.cc on {config.BaseUrl}", ["read:account"], MastodonRedirectUri(instance) ) @@ -60,19 +60,19 @@ public partial class FediverseAuthService { app = new FediverseApplication { - Id = existingAppId ?? _snowflakeGenerator.GenerateSnowflake(), + Id = existingAppId ?? snowflakeGenerator.GenerateSnowflake(), ClientId = misskeyApp.Id, ClientSecret = misskeyApp.Secret, Domain = instance, InstanceType = FediverseInstanceType.MisskeyApi, }; - _db.Add(app); + db.Add(app); } else { app = - await _db.FediverseApplications.FindAsync(existingAppId) + await db.FediverseApplications.FindAsync(existingAppId) ?? throw new FoxnounsError($"Existing app with ID {existingAppId} was null"); app.ClientId = misskeyApp.Id; @@ -80,7 +80,7 @@ public partial class FediverseAuthService app.InstanceType = FediverseInstanceType.MisskeyApi; } - await _db.SaveChangesAsync(); + await db.SaveChangesAsync(); return app; } @@ -96,7 +96,7 @@ public partial class FediverseAuthService private async Task GetMisskeyUserAsync(FediverseApplication app, string code) { - HttpResponseMessage resp = await _client.PostAsJsonAsync( + HttpResponseMessage resp = await client.PostAsJsonAsync( MisskeyTokenUri(app.Domain), new GetMisskeySessionUserKeyRequest(app.ClientSecret, code) ); @@ -130,7 +130,7 @@ public partial class FediverseAuthService app = await CreateMisskeyApplicationAsync(app.Domain, app.Id); } - HttpResponseMessage resp = await _client.PostAsJsonAsync( + HttpResponseMessage resp = await client.PostAsJsonAsync( MisskeyGenerateSessionUri(app.Domain), new CreateMisskeySessionUriRequest(app.ClientSecret) ); diff --git a/Foxnouns.Backend/Services/Auth/FediverseAuthService.cs b/Foxnouns.Backend/Services/Auth/FediverseAuthService.cs index 49afe1d..3ffc28d 100644 --- a/Foxnouns.Backend/Services/Auth/FediverseAuthService.cs +++ b/Foxnouns.Backend/Services/Auth/FediverseAuthService.cs @@ -19,37 +19,17 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; namespace Foxnouns.Backend.Services.Auth; -public partial class FediverseAuthService +public partial class FediverseAuthService( + ILogger logger, + Config config, + DatabaseContext db, + HttpClient client, + KeyCacheService keyCacheService, + ISnowflakeGenerator snowflakeGenerator +) { private const string NodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/2.0"; - - private readonly HttpClient _client; - private readonly ILogger _logger; - private readonly Config _config; - private readonly DatabaseContext _db; - private readonly KeyCacheService _keyCacheService; - private readonly ISnowflakeGenerator _snowflakeGenerator; - - public FediverseAuthService( - ILogger logger, - Config config, - DatabaseContext db, - KeyCacheService keyCacheService, - ISnowflakeGenerator snowflakeGenerator - ) - { - _logger = logger.ForContext(); - _config = config; - _db = db; - _keyCacheService = keyCacheService; - _snowflakeGenerator = snowflakeGenerator; - - _client = new HttpClient(); - _client.DefaultRequestHeaders.Remove("User-Agent"); - _client.DefaultRequestHeaders.Remove("Accept"); - _client.DefaultRequestHeaders.Add("User-Agent", $"pronouns.cc/{BuildInfo.Version}"); - _client.DefaultRequestHeaders.Add("Accept", "application/json"); - } + private readonly ILogger _logger = logger.ForContext(); public async Task GenerateAuthUrlAsync( string instance, @@ -70,7 +50,7 @@ public partial class FediverseAuthService public async Task GetApplicationAsync(string instance) { - FediverseApplication? app = await _db.FediverseApplications.FirstOrDefaultAsync(a => + FediverseApplication? app = await db.FediverseApplications.FirstOrDefaultAsync(a => a.Domain == instance ); if (app != null) @@ -92,7 +72,7 @@ public partial class FediverseAuthService { _logger.Debug("Requesting software name for fediverse instance {Instance}", instance); - HttpResponseMessage wellKnownResp = await _client.GetAsync( + HttpResponseMessage wellKnownResp = await client.GetAsync( new Uri($"https://{instance}/.well-known/nodeinfo") ); wellKnownResp.EnsureSuccessStatusCode(); @@ -107,7 +87,7 @@ public partial class FediverseAuthService ); } - HttpResponseMessage nodeInfoResp = await _client.GetAsync(nodeInfoUrl); + HttpResponseMessage nodeInfoResp = await client.GetAsync(nodeInfoUrl); nodeInfoResp.EnsureSuccessStatusCode(); PartialNodeInfo? nodeInfo = await nodeInfoResp.Content.ReadFromJsonAsync(); diff --git a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Discord.cs b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Discord.cs index de16d2f..d884fda 100644 --- a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Discord.cs +++ b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Discord.cs @@ -27,7 +27,7 @@ public partial class RemoteAuthService ) { var redirectUri = $"{config.BaseUrl}/auth/callback/discord"; - HttpResponseMessage resp = await _httpClient.PostAsync( + HttpResponseMessage resp = await client.PostAsync( _discordTokenUri, new FormUrlEncodedContent( new Dictionary @@ -59,7 +59,7 @@ public partial class RemoteAuthService var req = new HttpRequestMessage(HttpMethod.Get, _discordUserUri); req.Headers.Add("Authorization", $"{token.TokenType} {token.AccessToken}"); - HttpResponseMessage resp2 = await _httpClient.SendAsync(req, ct); + HttpResponseMessage resp2 = await client.SendAsync(req, ct); resp2.EnsureSuccessStatusCode(); DiscordUserResponse? user = await resp2.Content.ReadFromJsonAsync(ct); if (user == null) diff --git a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Google.cs b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Google.cs index 3245858..938ba32 100644 --- a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Google.cs +++ b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Google.cs @@ -28,7 +28,7 @@ public partial class RemoteAuthService ) { var redirectUri = $"{config.BaseUrl}/auth/callback/google"; - HttpResponseMessage resp = await _httpClient.PostAsync( + HttpResponseMessage resp = await client.PostAsync( _googleTokenUri, new FormUrlEncodedContent( new Dictionary diff --git a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Tumblr.cs b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Tumblr.cs index d63ee1a..45b9161 100644 --- a/Foxnouns.Backend/Services/Auth/RemoteAuthService.Tumblr.cs +++ b/Foxnouns.Backend/Services/Auth/RemoteAuthService.Tumblr.cs @@ -29,7 +29,7 @@ public partial class RemoteAuthService ) { var redirectUri = $"{config.BaseUrl}/auth/callback/tumblr"; - HttpResponseMessage resp = await _httpClient.PostAsync( + HttpResponseMessage resp = await client.PostAsync( _tumblrTokenUri, new FormUrlEncodedContent( new Dictionary @@ -62,7 +62,7 @@ public partial class RemoteAuthService var req = new HttpRequestMessage(HttpMethod.Get, _tumblrUserUri); req.Headers.Add("Authorization", $"Bearer {token.AccessToken}"); - HttpResponseMessage resp2 = await _httpClient.SendAsync(req, ct); + HttpResponseMessage resp2 = await client.SendAsync(req, ct); if (!resp2.IsSuccessStatusCode) { string respBody = await resp2.Content.ReadAsStringAsync(ct); diff --git a/Foxnouns.Backend/Services/Auth/RemoteAuthService.cs b/Foxnouns.Backend/Services/Auth/RemoteAuthService.cs index 6e0ba76..93b006b 100644 --- a/Foxnouns.Backend/Services/Auth/RemoteAuthService.cs +++ b/Foxnouns.Backend/Services/Auth/RemoteAuthService.cs @@ -25,6 +25,7 @@ using Microsoft.EntityFrameworkCore; namespace Foxnouns.Backend.Services.Auth; public partial class RemoteAuthService( + HttpClient client, Config config, ILogger logger, DatabaseContext db, @@ -32,7 +33,6 @@ public partial class RemoteAuthService( ) { private readonly ILogger _logger = logger.ForContext(); - private readonly HttpClient _httpClient = new(); public record RemoteUser(string Id, string Username); diff --git a/Foxnouns.Backend/Services/Caching/NoticeCacheService.cs b/Foxnouns.Backend/Services/Caching/NoticeCacheService.cs new file mode 100644 index 0000000..2a0b1f9 --- /dev/null +++ b/Foxnouns.Backend/Services/Caching/NoticeCacheService.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Foxnouns.Backend.Services.Caching; + +public class NoticeCacheService(IServiceProvider serviceProvider, IClock clock, ILogger logger) + : SingletonCacheService(serviceProvider, clock, logger) +{ + public override Duration MaxAge { get; init; } = Duration.FromMinutes(5); + + public override Func< + DatabaseContext, + CancellationToken, + Task + > FetchFunc { get; init; } = + async (db, ct) => + await db + .Notices.Where(n => + n.StartTime < clock.GetCurrentInstant() && n.EndTime > clock.GetCurrentInstant() + ) + .OrderByDescending(n => n.Id) + .FirstOrDefaultAsync(ct); +} diff --git a/Foxnouns.Backend/Services/Caching/SingletonCacheService.cs b/Foxnouns.Backend/Services/Caching/SingletonCacheService.cs new file mode 100644 index 0000000..87b19a7 --- /dev/null +++ b/Foxnouns.Backend/Services/Caching/SingletonCacheService.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using NodaTime; + +namespace Foxnouns.Backend.Services.Caching; + +public abstract class SingletonCacheService( + IServiceProvider serviceProvider, + IClock clock, + ILogger logger +) + where T : class +{ + private T? _item; + private Instant _lastUpdated = Instant.MinValue; + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly ILogger _logger = logger.ForContext>(); + + public virtual Duration MaxAge { get; init; } = Duration.FromMinutes(5); + + public virtual Func> FetchFunc { get; init; } = + (_, __) => Task.FromResult(null); + + public async Task GetAsync(CancellationToken ct = default) + { + await _semaphore.WaitAsync(ct); + try + { + if (_lastUpdated > clock.GetCurrentInstant() - MaxAge) + { + return _item; + } + + _logger.Debug("Cached item of type {Type} is expired, fetching it.", typeof(T)); + + await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope(); + await using DatabaseContext db = + scope.ServiceProvider.GetRequiredService(); + + T? item = await FetchFunc(db, ct); + _item = item; + _lastUpdated = clock.GetCurrentInstant(); + return item; + } + finally + { + _semaphore.Release(); + } + } +} diff --git a/Foxnouns.Backend/Services/DataCleanupService.cs b/Foxnouns.Backend/Services/DataCleanupService.cs index 3d60462..ee60bb8 100644 --- a/Foxnouns.Backend/Services/DataCleanupService.cs +++ b/Foxnouns.Backend/Services/DataCleanupService.cs @@ -128,5 +128,5 @@ public class DataCleanupService( } private static string ExportPath(Snowflake userId, string b64) => - $"data-exports/{userId}/{b64}.zip"; + $"data-exports/{userId}/{b64}/data-export.zip"; } diff --git a/Foxnouns.Backend/Services/EmailRateLimiter.cs b/Foxnouns.Backend/Services/EmailRateLimiter.cs index 3a1a81a..cc2dbb4 100644 --- a/Foxnouns.Backend/Services/EmailRateLimiter.cs +++ b/Foxnouns.Backend/Services/EmailRateLimiter.cs @@ -23,8 +23,11 @@ public class EmailRateLimiter { private readonly ConcurrentDictionary _limiters = new(); - private readonly FixedWindowRateLimiterOptions _limiterOptions = - new() { Window = TimeSpan.FromHours(2), PermitLimit = 3 }; + private readonly FixedWindowRateLimiterOptions _limiterOptions = new() + { + Window = TimeSpan.FromHours(2), + PermitLimit = 3, + }; private RateLimiter GetLimiter(string bucket) => _limiters.GetOrAdd(bucket, _ => new FixedWindowRateLimiter(_limiterOptions)); diff --git a/Foxnouns.Backend/Services/KeyCacheService.cs b/Foxnouns.Backend/Services/KeyCacheService.cs index 0163516..5c59bce 100644 --- a/Foxnouns.Backend/Services/KeyCacheService.cs +++ b/Foxnouns.Backend/Services/KeyCacheService.cs @@ -17,94 +17,39 @@ using Foxnouns.Backend.Database.Models; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using NodaTime; +using StackExchange.Redis; namespace Foxnouns.Backend.Services; -public class KeyCacheService(DatabaseContext db, IClock clock, ILogger logger) +public class KeyCacheService(Config config) { - private readonly ILogger _logger = logger.ForContext(); + public ConnectionMultiplexer Multiplexer { get; } = + ConnectionMultiplexer.Connect(config.Database.Redis); - public Task SetKeyAsync( - string key, - string value, - Duration expireAfter, - CancellationToken ct = default - ) => SetKeyAsync(key, value, clock.GetCurrentInstant() + expireAfter, ct); + public async Task SetKeyAsync(string key, string value, Duration expireAfter) => + await Multiplexer + .GetDatabase() + .StringSetAsync(key, value, expiry: expireAfter.ToTimeSpan()); - public async Task SetKeyAsync( - string key, - string value, - Instant expires, - CancellationToken ct = default - ) - { - db.TemporaryKeys.Add( - new TemporaryKey - { - Expires = expires, - Key = key, - Value = value, - } - ); - await db.SaveChangesAsync(ct); - } + public async Task GetKeyAsync(string key, bool delete = false) => + delete + ? await Multiplexer.GetDatabase().StringGetDeleteAsync(key) + : await Multiplexer.GetDatabase().StringGetAsync(key); - public async Task GetKeyAsync( - string key, - bool delete = false, - CancellationToken ct = default - ) - { - TemporaryKey? value = await db.TemporaryKeys.FirstOrDefaultAsync(k => k.Key == key, ct); - if (value == null) - return null; + public async Task DeleteKeyAsync(string key) => + await Multiplexer.GetDatabase().KeyDeleteAsync(key); - if (delete) - await db.TemporaryKeys.Where(k => k.Key == key).ExecuteDeleteAsync(ct); - - return value.Value; - } - - public async Task DeleteKeyAsync(string key, CancellationToken ct = default) => - await db.TemporaryKeys.Where(k => k.Key == key).ExecuteDeleteAsync(ct); - - public async Task DeleteExpiredKeysAsync(CancellationToken ct) - { - int count = await db - .TemporaryKeys.Where(k => k.Expires < clock.GetCurrentInstant()) - .ExecuteDeleteAsync(ct); - if (count != 0) - _logger.Information("Removed {Count} expired keys from the database", count); - } - - public Task SetKeyAsync( - string key, - T obj, - Duration expiresAt, - CancellationToken ct = default - ) - where T : class => SetKeyAsync(key, obj, clock.GetCurrentInstant() + expiresAt, ct); - - public async Task SetKeyAsync( - string key, - T obj, - Instant expires, - CancellationToken ct = default - ) + public async Task SetKeyAsync(string key, T obj, Duration expiresAt) where T : class { string value = JsonConvert.SerializeObject(obj); - await SetKeyAsync(key, value, expires, ct); + await SetKeyAsync(key, value, expiresAt); } - public async Task GetKeyAsync( - string key, - bool delete = false, - CancellationToken ct = default - ) + public async Task GetKeyAsync(string key, bool delete = false) where T : class { - string? value = await GetKeyAsync(key, delete, ct); + string? value = await GetKeyAsync(key, delete); return value == null ? default : JsonConvert.DeserializeObject(value); } } diff --git a/Foxnouns.Backend/Services/ModerationRendererService.cs b/Foxnouns.Backend/Services/ModerationRendererService.cs index deed9c5..c1d259a 100644 --- a/Foxnouns.Backend/Services/ModerationRendererService.cs +++ b/Foxnouns.Backend/Services/ModerationRendererService.cs @@ -36,6 +36,7 @@ public class ModerationRendererService( : null, report.Status, report.Reason, + report.Context, report.TargetType, report.TargetSnapshot != null ? JsonConvert.DeserializeObject(report.TargetSnapshot) @@ -45,12 +46,26 @@ public class ModerationRendererService( public AuditLogResponse RenderAuditLogEntry(AuditLogEntry entry) { + PartialReport? report = null; + if (entry.Report != null) + { + report = new PartialReport( + entry.Report.Id, + entry.Report.ReporterId, + entry.Report.TargetUserId, + entry.Report.TargetMemberId, + entry.Report.Reason, + entry.Report.Context, + entry.Report.TargetType + ); + } + return new AuditLogResponse( Id: entry.Id, Moderator: ToEntity(entry.ModeratorId, entry.ModeratorUsername)!, TargetUser: ToEntity(entry.TargetUserId, entry.TargetUsername), TargetMember: ToEntity(entry.TargetMemberId, entry.TargetMemberName), - ReportId: entry.ReportId, + Report: report, Type: entry.Type, Reason: entry.Reason, ClearedFields: entry.ClearedFields diff --git a/Foxnouns.Backend/Services/ModerationService.cs b/Foxnouns.Backend/Services/ModerationService.cs index 5444657..30d99ed 100644 --- a/Foxnouns.Backend/Services/ModerationService.cs +++ b/Foxnouns.Backend/Services/ModerationService.cs @@ -18,6 +18,7 @@ using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; using Foxnouns.Backend.Jobs; using Humanizer; +using Microsoft.EntityFrameworkCore; using NodaTime; namespace Foxnouns.Backend.Services; @@ -26,7 +27,6 @@ public class ModerationService( ILogger logger, DatabaseContext db, ISnowflakeGenerator snowflakeGenerator, - IQueue queue, IClock clock ) { @@ -63,6 +63,54 @@ public class ModerationService( return entry; } + public async Task QuerySensitiveDataAsync( + User moderator, + User target, + string reason + ) + { + _logger.Information( + "Moderator {ModeratorId} is querying sensitive data for {TargetId}", + moderator.Id, + target.Id + ); + + var entry = new AuditLogEntry + { + Id = snowflakeGenerator.GenerateSnowflake(), + ModeratorId = moderator.Id, + ModeratorUsername = moderator.Username, + TargetUserId = target.Id, + TargetUsername = target.Username, + Type = AuditLogEntryType.QuerySensitiveUserData, + Reason = reason, + }; + db.AuditLog.Add(entry); + + await db.SaveChangesAsync(); + return entry; + } + + public async Task ShowSensitiveDataAsync( + User moderator, + User target, + CancellationToken ct = default + ) + { + Snowflake cutoff = snowflakeGenerator.GenerateSnowflake( + clock.GetCurrentInstant() - Duration.FromDays(1) + ); + + return await db.AuditLog.AnyAsync( + e => + e.ModeratorId == moderator.Id + && e.TargetUserId == target.Id + && e.Type == AuditLogEntryType.QuerySensitiveUserData + && e.Id > cutoff, + ct + ); + } + public async Task ExecuteSuspensionAsync( User moderator, User target, @@ -105,6 +153,12 @@ public class ModerationService( target.DeletedAt = clock.GetCurrentInstant(); target.DeletedBy = moderator.Id; + if (report != null) + { + report.Status = ReportStatus.Closed; + db.Update(report); + } + if (!clearProfile) { db.Update(target); @@ -126,9 +180,7 @@ public class ModerationService( target.CustomPreferences = []; target.ProfileFlags = []; - queue.QueueInvocableWithPayload( - new AvatarUpdatePayload(target.Id, null) - ); + UserAvatarUpdateJob.Enqueue(new AvatarUpdatePayload(target.Id, null)); // TODO: also clear member profiles? @@ -209,10 +261,9 @@ public class ModerationService( targetMember.DisplayName = null; break; case FieldsToClear.Avatar: - queue.QueueInvocableWithPayload< - MemberAvatarUpdateInvocable, - AvatarUpdatePayload - >(new AvatarUpdatePayload(targetMember.Id, null)); + MemberAvatarUpdateJob.Enqueue( + new AvatarUpdatePayload(targetMember.Id, null) + ); break; case FieldsToClear.Bio: targetMember.Bio = null; @@ -251,10 +302,7 @@ public class ModerationService( targetUser.DisplayName = null; break; case FieldsToClear.Avatar: - queue.QueueInvocableWithPayload< - UserAvatarUpdateInvocable, - AvatarUpdatePayload - >(new AvatarUpdatePayload(targetUser.Id, null)); + UserAvatarUpdateJob.Enqueue(new AvatarUpdatePayload(targetUser.Id, null)); break; case FieldsToClear.Bio: targetUser.Bio = null; @@ -285,6 +333,12 @@ public class ModerationService( db.Update(targetUser); } + if (report != null) + { + report.Status = ReportStatus.Closed; + db.Update(report); + } + await db.SaveChangesAsync(); return entry; diff --git a/Foxnouns.Backend/Services/PeriodicTasksService.cs b/Foxnouns.Backend/Services/PeriodicTasksService.cs index bf0f4af..e5efd28 100644 --- a/Foxnouns.Backend/Services/PeriodicTasksService.cs +++ b/Foxnouns.Backend/Services/PeriodicTasksService.cs @@ -33,11 +33,9 @@ public class PeriodicTasksService(ILogger logger, IServiceProvider services) : B // The type is literally written on the same line, we can just use `var` // ReSharper disable SuggestVarOrType_SimpleTypes - var keyCacheService = scope.ServiceProvider.GetRequiredService(); var dataCleanupService = scope.ServiceProvider.GetRequiredService(); // ReSharper restore SuggestVarOrType_SimpleTypes - await keyCacheService.DeleteExpiredKeysAsync(ct); await dataCleanupService.InvokeAsync(ct); } } diff --git a/Foxnouns.Backend/Services/UserRendererService.cs b/Foxnouns.Backend/Services/UserRendererService.cs index df40e1a..0c1fc1b 100644 --- a/Foxnouns.Backend/Services/UserRendererService.cs +++ b/Foxnouns.Backend/Services/UserRendererService.cs @@ -33,6 +33,7 @@ public class UserRendererService( bool renderMembers = true, bool renderAuthMethods = false, string? overrideSid = null, + bool renderSettings = false, CancellationToken ct = default ) => await RenderUserInnerAsync( @@ -42,6 +43,7 @@ public class UserRendererService( renderMembers, renderAuthMethods, overrideSid, + renderSettings, ct ); @@ -52,6 +54,7 @@ public class UserRendererService( bool renderMembers = true, bool renderAuthMethods = false, string? overrideSid = null, + bool renderSettings = false, CancellationToken ct = default ) { @@ -62,6 +65,7 @@ public class UserRendererService( renderMembers = renderMembers && (!user.ListHidden || tokenCanReadHiddenMembers); renderAuthMethods = renderAuthMethods && tokenPrivileged; + renderSettings = renderSettings && tokenHidden; IEnumerable members = renderMembers ? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct) @@ -103,7 +107,8 @@ public class UserRendererService( user.Names, user.Pronouns, user.Fields, - user.CustomPreferences, + user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value))) + .ToDictionary(), flags.Select(f => RenderPrideFlag(f.PrideFlag)), utcOffset, user.Role, @@ -116,7 +121,8 @@ public class UserRendererService( tokenHidden ? user.LastSidReroll : null, tokenHidden ? user.Timezone ?? "" : null, tokenHidden ? user is { Deleted: true, DeletedBy: not null } : null, - tokenHidden ? user.Deleted : null + tokenHidden ? user.Deleted : null, + renderSettings ? user.Settings : null ); } @@ -130,6 +136,14 @@ public class UserRendererService( : a.RemoteUsername ); + public static CustomPreferenceResponse RenderCustomPreference(User.CustomPreference pref) => + new(pref.Icon, pref.Tooltip, pref.Muted, pref.Favourite, pref.Size); + + public static Dictionary RenderCustomPreferences( + User user + ) => + user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value))).ToDictionary(); + public PartialUser RenderPartialUser(User user) => new( user.Id, diff --git a/Foxnouns.Backend/Services/V1/MembersV1Service.cs b/Foxnouns.Backend/Services/V1/MembersV1Service.cs new file mode 100644 index 0000000..632226c --- /dev/null +++ b/Foxnouns.Backend/Services/V1/MembersV1Service.cs @@ -0,0 +1,125 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Dto.V1; +using Microsoft.EntityFrameworkCore; +using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry; +using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag; + +namespace Foxnouns.Backend.Services.V1; + +public class MembersV1Service(DatabaseContext db, UsersV1Service usersV1Service) +{ + public async Task ResolveMemberAsync(string id, CancellationToken ct = default) + { + Member? member; + if (Snowflake.TryParse(id, out Snowflake? sf)) + { + member = await db + .Members.Include(m => m.User) + .FirstOrDefaultAsync(m => m.Id == sf && !m.User.Deleted, ct); + if (member != null) + return member; + } + + member = await db + .Members.Include(m => m.User) + .FirstOrDefaultAsync(m => m.LegacyId == id && !m.User.Deleted, ct); + if (member != null) + return member; + + throw new ApiError.NotFound("No member with that ID found.", ErrorCode.MemberNotFound); + } + + public async Task ResolveMemberAsync( + string userRef, + string memberRef, + Token? token, + CancellationToken ct = default + ) + { + User user = await usersV1Service.ResolveUserAsync(userRef, token, ct); + + Member? member; + if (Snowflake.TryParse(memberRef, out Snowflake? sf)) + { + member = await db + .Members.Include(m => m.User) + .FirstOrDefaultAsync(m => m.Id == sf && m.UserId == user.Id, ct); + if (member != null) + return member; + } + + member = await db + .Members.Include(m => m.User) + .FirstOrDefaultAsync(m => m.LegacyId == memberRef && m.UserId == user.Id, ct); + if (member != null) + return member; + + member = await db + .Members.Include(m => m.User) + .FirstOrDefaultAsync(m => m.Name == memberRef && m.UserId == user.Id, ct); + if (member != null) + return member; + + throw new ApiError.NotFound( + "No member with that ID or name found.", + ErrorCode.MemberNotFound + ); + } + + public async Task RenderMemberAsync( + Member m, + Token? token = default, + User? user = null, + bool renderFlags = true, + CancellationToken ct = default + ) + { + user ??= m.User; + bool renderUnlisted = m.UserId == token?.UserId; + + List flags = renderFlags + ? await db.MemberFlags.Where(f => f.MemberId == m.Id).OrderBy(f => f.Id).ToListAsync(ct) + : []; + + return new MemberResponse( + m.LegacyId, + m.Id, + m.Sid, + m.Name, + m.DisplayName, + m.Bio, + m.Avatar, + m.Links, + Names: FieldEntry.FromEntries(m.Names, user.CustomPreferences), + Pronouns: PronounEntry.FromPronouns(m.Pronouns, user.CustomPreferences), + Fields: ProfileField.FromFields(m.Fields, user.CustomPreferences), + Flags: flags + .Where(f => f.PrideFlag.Hash != null) + .Select(f => new PrideFlag( + f.PrideFlag.LegacyId, + f.PrideFlag.Id, + f.PrideFlag.Hash!, + f.PrideFlag.Name, + f.PrideFlag.Description + )) + .ToArray(), + User: UsersV1Service.RenderPartialUser(user), + Unlisted: renderUnlisted ? m.Unlisted : null + ); + } +} diff --git a/Foxnouns.Backend/Services/V1/UsersV1Service.cs b/Foxnouns.Backend/Services/V1/UsersV1Service.cs new file mode 100644 index 0000000..1f2ad79 --- /dev/null +++ b/Foxnouns.Backend/Services/V1/UsersV1Service.cs @@ -0,0 +1,247 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; +using Foxnouns.Backend.Dto.V1; +using Microsoft.EntityFrameworkCore; +using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry; +using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag; + +namespace Foxnouns.Backend.Services.V1; + +public class UsersV1Service(DatabaseContext db) +{ + public async Task ResolveUserAsync( + string userRef, + Token? token, + CancellationToken ct = default + ) + { + if (userRef == "@me") + { + if (token == null) + { + throw new ApiError.Unauthorized( + "This endpoint requires an authenticated user.", + ErrorCode.AuthenticationRequired + ); + } + + return await db.Users.FirstAsync(u => u.Id == token.UserId, ct); + } + + User? user; + if (Snowflake.TryParse(userRef, out Snowflake? sf)) + { + user = await db.Users.FirstOrDefaultAsync(u => u.Id == sf && !u.Deleted, ct); + if (user != null) + return user; + } + + user = await db.Users.FirstOrDefaultAsync(u => u.LegacyId == userRef && !u.Deleted, ct); + if (user != null) + return user; + + user = await db.Users.FirstOrDefaultAsync(u => u.Username == userRef && !u.Deleted, ct); + if (user != null) + return user; + + throw new ApiError.NotFound( + "No user with that ID or username found.", + ErrorCode.UserNotFound + ); + } + + public async Task RenderUserAsync( + User user, + Token? token = null, + bool renderMembers = true, + bool renderFlags = true, + CancellationToken ct = default + ) + { + bool isSelfUser = user.Id == token?.UserId; + renderMembers = renderMembers && (isSelfUser || !user.ListHidden); + + // Only fetch members if we're rendering members (duh) + List members = renderMembers + ? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct) + : []; + + List flags = renderFlags + ? await db.UserFlags.Where(f => f.UserId == user.Id).OrderBy(f => f.Id).ToListAsync(ct) + : []; + + int? utcOffset = null; + if ( + user.Timezone != null + && TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz) + ) + { + utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds; + } + + return new UserResponse( + user.LegacyId, + user.Id, + user.Sid, + user.Username, + user.DisplayName, + user.Bio, + user.MemberTitle, + user.Avatar, + user.Links, + Names: FieldEntry.FromEntries(user.Names, user.CustomPreferences), + Pronouns: PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences), + Fields: ProfileField.FromFields(user.Fields, user.CustomPreferences), + Flags: flags + .Where(f => f.PrideFlag.Hash != null) + .Select(f => new PrideFlag( + f.PrideFlag.LegacyId, + f.PrideFlag.Id, + f.PrideFlag.Hash!, + f.PrideFlag.Name, + f.PrideFlag.Description + )) + .ToArray(), + Members: members.Select(m => RenderPartialMember(m, user.CustomPreferences)).ToArray(), + utcOffset, + CustomPreferences: RenderCustomPreferences(user.CustomPreferences) + ); + } + + public async Task RenderCurrentUserAsync( + User user, + CancellationToken ct = default + ) + { + List members = await db + .Members.Where(m => m.UserId == user.Id) + .OrderBy(m => m.Name) + .ToListAsync(ct); + + List flags = await db + .UserFlags.Where(f => f.UserId == user.Id) + .OrderBy(f => f.Id) + .ToListAsync(ct); + + int? utcOffset = null; + if ( + user.Timezone != null + && TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz) + ) + { + utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds; + } + + List authMethods = await db + .AuthMethods.Include(a => a.FediverseApplication) + .Where(a => a.UserId == user.Id) + .OrderBy(a => a.Id) + .ToListAsync(ct); + + AuthMethod? discord = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Discord); + AuthMethod? google = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Google); + AuthMethod? tumblr = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Tumblr); + AuthMethod? fediverse = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Fediverse); + + return new CurrentUserResponse( + user.LegacyId, + user.Id, + user.Sid, + user.Username, + user.DisplayName, + user.Bio, + user.MemberTitle, + user.Avatar, + user.Links, + Names: FieldEntry.FromEntries(user.Names, user.CustomPreferences), + Pronouns: PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences), + Fields: ProfileField.FromFields(user.Fields, user.CustomPreferences), + Flags: flags + .Where(f => f.PrideFlag.Hash != null) + .Select(f => new PrideFlag( + f.PrideFlag.LegacyId, + f.PrideFlag.Id, + f.PrideFlag.Hash!, + f.PrideFlag.Name, + f.PrideFlag.Description + )) + .ToArray(), + Members: members.Select(m => RenderPartialMember(m, user.CustomPreferences)).ToArray(), + utcOffset, + CustomPreferences: RenderCustomPreferences(user.CustomPreferences), + user.Id.Time, + user.Timezone, + user.Role is UserRole.Admin, + user.ListHidden, + user.LastSidReroll, + discord?.RemoteId, + discord?.RemoteUsername, + google?.RemoteId, + google?.RemoteUsername, + tumblr?.RemoteId, + tumblr?.RemoteUsername, + fediverse?.RemoteId, + fediverse?.RemoteUsername, + fediverse?.FediverseApplication?.Domain + ); + } + + private static Dictionary RenderCustomPreferences( + Dictionary customPreferences + ) => + customPreferences + .Select(x => + ( + x.Value.LegacyId, + new CustomPreference( + x.Value.Icon, + x.Value.Tooltip, + x.Value.Size, + x.Value.Muted, + x.Value.Favourite + ) + ) + ) + .ToDictionary(); + + private static PartialMember RenderPartialMember( + Member m, + Dictionary customPreferences + ) => + new( + m.LegacyId, + m.Id, + m.Sid, + m.Name, + m.DisplayName, + m.Bio, + m.Avatar, + m.Links, + Names: FieldEntry.FromEntries(m.Names, customPreferences), + Pronouns: PronounEntry.FromPronouns(m.Pronouns, customPreferences) + ); + + public static PartialUser RenderPartialUser(User user) => + new( + user.LegacyId, + user.Id, + user.Username, + user.DisplayName, + user.Avatar, + CustomPreferences: RenderCustomPreferences(user.CustomPreferences) + ); +} diff --git a/Foxnouns.Backend/Database/Models/TemporaryKey.cs b/Foxnouns.Backend/Services/V1/V1Utils.cs similarity index 58% rename from Foxnouns.Backend/Database/Models/TemporaryKey.cs rename to Foxnouns.Backend/Services/V1/V1Utils.cs index f83e515..2e52316 100644 --- a/Foxnouns.Backend/Database/Models/TemporaryKey.cs +++ b/Foxnouns.Backend/Services/V1/V1Utils.cs @@ -12,14 +12,23 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using NodaTime; +using Foxnouns.Backend.Database; +using Foxnouns.Backend.Database.Models; -namespace Foxnouns.Backend.Database.Models; +namespace Foxnouns.Backend.Services.V1; -public class TemporaryKey +public static class V1Utils { - public long Id { get; init; } - public required string Key { get; init; } - public required string Value { get; set; } - public Instant Expires { get; init; } + public static string TranslateStatus( + string status, + Dictionary customPreferences + ) + { + if (!Snowflake.TryParse(status, out Snowflake? sf)) + return status; + + return customPreferences.TryGetValue(sf.Value, out User.CustomPreference? cf) + ? cf.LegacyId.ToString() + : "unknown"; + } } diff --git a/Foxnouns.Backend/Utils/ValidationUtils.Fields.cs b/Foxnouns.Backend/Services/ValidationService.Fields.cs similarity index 56% rename from Foxnouns.Backend/Utils/ValidationUtils.Fields.cs rename to Foxnouns.Backend/Services/ValidationService.Fields.cs index 0235eb6..e2cbff3 100644 --- a/Foxnouns.Backend/Utils/ValidationUtils.Fields.cs +++ b/Foxnouns.Backend/Services/ValidationService.Fields.cs @@ -15,9 +15,9 @@ using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; -namespace Foxnouns.Backend.Utils; +namespace Foxnouns.Backend.Services; -public static partial class ValidationUtils +public partial class ValidationService { public static readonly string[] DefaultStatusOptions = [ @@ -28,7 +28,7 @@ public static partial class ValidationUtils "avoid", ]; - public static IEnumerable<(string, ValidationError?)> ValidateFields( + public IEnumerable<(string, ValidationError?)> ValidateFields( List? fields, IReadOnlyDictionary customPreferences ) @@ -37,7 +37,7 @@ public static partial class ValidationUtils return []; var errors = new List<(string, ValidationError?)>(); - if (fields.Count > 25) + if (fields.Count > _limits.MaxFields) { errors.Add( ( @@ -45,7 +45,7 @@ public static partial class ValidationUtils ValidationError.LengthError( "Too many fields", 0, - Limits.FieldLimit, + _limits.MaxFields, fields.Count ) ) @@ -53,39 +53,38 @@ public static partial class ValidationUtils } // No overwhelming this function, thank you - if (fields.Count > 100) + if (fields.Count > _limits.MaxFields + 50) return errors; foreach ((Field? field, int index) in fields.Select((field, index) => (field, index))) { - switch (field.Name.Length) + if (field.Name.Length > _limits.MaxFieldNameLength) { - case > Limits.FieldNameLimit: - errors.Add( - ( - $"fields.{index}.name", - ValidationError.LengthError( - "Field name is too long", - 1, - Limits.FieldNameLimit, - field.Name.Length - ) + errors.Add( + ( + $"fields.{index}.name", + ValidationError.LengthError( + "Field name is too long", + 1, + _limits.MaxFieldNameLength, + field.Name.Length ) - ); - break; - case < 1: - errors.Add( - ( - $"fields.{index}.name", - ValidationError.LengthError( - "Field name is too short", - 1, - Limits.FieldNameLimit, - field.Name.Length - ) + ) + ); + } + else if (field.Name.Length < 1) + { + errors.Add( + ( + $"fields.{index}.name", + ValidationError.LengthError( + "Field name is too short", + 1, + _limits.MaxFieldNameLength, + field.Name.Length ) - ); - break; + ) + ); } errors = errors @@ -102,7 +101,7 @@ public static partial class ValidationUtils return errors; } - public static IEnumerable<(string, ValidationError?)> ValidateFieldEntries( + public IEnumerable<(string, ValidationError?)> ValidateFieldEntries( FieldEntry[]? entries, IReadOnlyDictionary customPreferences, string errorPrefix = "fields" @@ -112,7 +111,7 @@ public static partial class ValidationUtils return []; var errors = new List<(string, ValidationError?)>(); - if (entries.Length > Limits.FieldEntriesLimit) + if (entries.Length > _limits.MaxFieldEntries) { errors.Add( ( @@ -120,7 +119,7 @@ public static partial class ValidationUtils ValidationError.LengthError( "Field has too many entries", 0, - Limits.FieldEntriesLimit, + _limits.MaxFieldEntries, entries.Length ) ) @@ -128,7 +127,7 @@ public static partial class ValidationUtils } // Same as above, no overwhelming this function with a ridiculous amount of entries - if (entries.Length > Limits.FieldEntriesLimit + 50) + if (entries.Length > _limits.MaxFieldEntries + 50) return errors; string[] customPreferenceIds = customPreferences.Keys.Select(id => id.ToString()).ToArray(); @@ -139,34 +138,33 @@ public static partial class ValidationUtils ) ) { - switch (entry.Value.Length) + if (entry.Value.Length > _limits.MaxFieldEntryTextLength) { - case > Limits.FieldEntryTextLimit: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.value", - ValidationError.LengthError( - "Field value is too long", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.value", + ValidationError.LengthError( + "Field value is too long", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; - case < 1: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.value", - ValidationError.LengthError( - "Field value is too short", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + ) + ); + } + else if (entry.Value.Length < 1) + { + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.value", + ValidationError.LengthError( + "Field value is too short", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; + ) + ); } if ( @@ -186,7 +184,7 @@ public static partial class ValidationUtils return errors; } - public static IEnumerable<(string, ValidationError?)> ValidatePronouns( + public IEnumerable<(string, ValidationError?)> ValidatePronouns( Pronoun[]? entries, IReadOnlyDictionary customPreferences, string errorPrefix = "pronouns" @@ -196,7 +194,7 @@ public static partial class ValidationUtils return []; var errors = new List<(string, ValidationError?)>(); - if (entries.Length > Limits.FieldEntriesLimit) + if (entries.Length > _limits.MaxFieldEntries) { errors.Add( ( @@ -204,7 +202,7 @@ public static partial class ValidationUtils ValidationError.LengthError( "Too many pronouns", 0, - Limits.FieldEntriesLimit, + _limits.MaxFieldEntries, entries.Length ) ) @@ -212,7 +210,7 @@ public static partial class ValidationUtils } // Same as above, no overwhelming this function with a ridiculous amount of entries - if (entries.Length > Limits.FieldEntriesLimit + 50) + if (entries.Length > _limits.MaxFieldEntries + 50) return errors; string[] customPreferenceIds = customPreferences.Keys.Select(id => id.ToString()).ToArray(); @@ -221,66 +219,64 @@ public static partial class ValidationUtils (Pronoun? entry, int entryIdx) in entries.Select((entry, entryIdx) => (entry, entryIdx)) ) { - switch (entry.Value.Length) + if (entry.Value.Length > _limits.MaxFieldEntryTextLength) { - case > Limits.FieldEntryTextLimit: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.value", - ValidationError.LengthError( - "Pronoun value is too long", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.value", + ValidationError.LengthError( + "Pronoun value is too long", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; - case < 1: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.value", - ValidationError.LengthError( - "Pronoun value is too short", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + ) + ); + } + else if (entry.Value.Length < 1) + { + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.value", + ValidationError.LengthError( + "Pronoun value is too short", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; + ) + ); } if (entry.DisplayText != null) { - switch (entry.DisplayText.Length) + if (entry.DisplayText.Length > _limits.MaxFieldEntryTextLength) { - case > Limits.FieldEntryTextLimit: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.display_text", - ValidationError.LengthError( - "Pronoun display text is too long", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.display_text", + ValidationError.LengthError( + "Pronoun display text is too long", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; - case < 1: - errors.Add( - ( - $"{errorPrefix}.{entryIdx}.display_text", - ValidationError.LengthError( - "Pronoun display text is too short", - 1, - Limits.FieldEntryTextLimit, - entry.Value.Length - ) + ) + ); + } + else if (entry.DisplayText.Length < 1) + { + errors.Add( + ( + $"{errorPrefix}.{entryIdx}.display_text", + ValidationError.LengthError( + "Pronoun display text is too short", + 1, + _limits.MaxFieldEntryTextLength, + entry.Value.Length ) - ); - break; + ) + ); } } diff --git a/Foxnouns.Backend/Services/ValidationService.Strings.cs b/Foxnouns.Backend/Services/ValidationService.Strings.cs new file mode 100644 index 0000000..9244ed4 --- /dev/null +++ b/Foxnouns.Backend/Services/ValidationService.Strings.cs @@ -0,0 +1,259 @@ +// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +using System.Text.RegularExpressions; + +namespace Foxnouns.Backend.Services; + +public partial class ValidationService +{ + private static readonly string[] InvalidUsernames = + [ + "..", + "admin", + "administrator", + "mod", + "moderator", + "api", + "page", + "pronouns", + "settings", + "pronouns.cc", + "pronounscc", + "null", + ]; + + private static readonly string[] InvalidMemberNames = + [ + // these break routing outright + ".", + "..", + // TODO: remove this? i'm not sure if /@[username]/edit will redirect to settings + "edit", + // this breaks the frontend, somehow + "null", + ]; + + public ValidationError? ValidateUsername(string username) + { + if (!UsernameRegex().IsMatch(username)) + { + if (username.Length < 2) + { + return ValidationError.LengthError( + "Username is too short", + 2, + _limits.MaxUsernameLength, + username.Length + ); + } + + if (username.Length > _limits.MaxUsernameLength) + { + return ValidationError.LengthError( + "Username is too long", + 2, + _limits.MaxUsernameLength, + username.Length + ); + } + + return ValidationError.GenericValidationError( + "Username is invalid, can only contain alphanumeric characters, dashes, underscores, and periods", + username + ); + } + + if ( + InvalidUsernames.Any(u => + string.Equals(u, username, StringComparison.InvariantCultureIgnoreCase) + ) + ) + { + return ValidationError.GenericValidationError("Username is not allowed", username); + } + + return null; + } + + public ValidationError? ValidateMemberName(string memberName) + { + if (!MemberRegex().IsMatch(memberName)) + { + if (memberName.Length < 1) + { + return ValidationError.LengthError( + "Name is too short", + 1, + _limits.MaxMemberNameLength, + memberName.Length + ); + } + + if (memberName.Length > _limits.MaxMemberNameLength) + { + return ValidationError.LengthError( + "Name is too long", + 1, + _limits.MaxMemberNameLength, + memberName.Length + ); + } + + return ValidationError.GenericValidationError( + "Member name cannot contain any of the following: " + + " @, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), {, }, +, <, =, >, ^, |, ~, `, , " + + "and cannot be one or two periods", + memberName + ); + } + + if ( + InvalidMemberNames.Any(u => + string.Equals(u, memberName, StringComparison.InvariantCultureIgnoreCase) + ) + ) + { + return ValidationError.GenericValidationError("Name is not allowed", memberName); + } + + return null; + } + + public ValidationError? ValidateDisplayName(string? displayName) + { + if (displayName?.Length == 0) + { + return ValidationError.LengthError( + "Display name is too short", + 1, + _limits.MaxDisplayNameLength, + displayName.Length + ); + } + + if (displayName?.Length > _limits.MaxDisplayNameLength) + { + return ValidationError.LengthError( + "Display name is too long", + 1, + _limits.MaxDisplayNameLength, + displayName.Length + ); + } + + return null; + } + + public IEnumerable<(string, ValidationError?)> ValidateLinks(string[]? links) + { + if (links == null) + return []; + if (links.Length > _limits.MaxLinks) + { + return + [ + ( + "links", + ValidationError.LengthError("Too many links", 0, _limits.MaxLinks, links.Length) + ), + ]; + } + + var errors = new List<(string, ValidationError?)>(); + foreach ((string link, int idx) in links.Select((l, i) => (l, i))) + { + if (link.Length == 0) + { + errors.Add( + ( + $"links.{idx}", + ValidationError.LengthError( + "Link cannot be empty", + 1, + _limits.MaxLinkLength, + 0 + ) + ) + ); + } + else if (link.Length > _limits.MaxLinkLength) + { + errors.Add( + ( + $"links.{idx}", + ValidationError.LengthError( + "Link is too long", + 1, + _limits.MaxLinkLength, + link.Length + ) + ) + ); + } + } + + return errors; + } + + public ValidationError? ValidateBio(string? bio) + { + if (bio?.Length == 0) + { + return ValidationError.LengthError( + "Bio is too short", + 1, + _limits.MaxBioLength, + bio.Length + ); + } + + if (bio?.Length > _limits.MaxBioLength) + { + return ValidationError.LengthError( + "Bio is too long", + 1, + _limits.MaxBioLength, + bio.Length + ); + } + + return null; + } + + public ValidationError? ValidateAvatar(string? avatar) + { + if (avatar?.Length == 0) + { + return ValidationError.GenericValidationError("Avatar cannot be empty", null); + } + + if (avatar?.Length > _limits.MaxAvatarLength) + { + return ValidationError.GenericValidationError("Avatar is too large", null); + } + + return null; + } + + [GeneratedRegex(@"^[a-zA-Z_0-9\-\.]{2,40}$", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex UsernameRegex(); + + [GeneratedRegex( + """^[^@'$%&()+<=>^|~`,*!#/\\\[\]""\{\}\?]{1,100}$""", + RegexOptions.IgnoreCase, + "en-US" + )] + private static partial Regex MemberRegex(); +} diff --git a/Foxnouns.Backend/Services/ValidationService.cs b/Foxnouns.Backend/Services/ValidationService.cs new file mode 100644 index 0000000..989f469 --- /dev/null +++ b/Foxnouns.Backend/Services/ValidationService.cs @@ -0,0 +1,6 @@ +namespace Foxnouns.Backend.Services; + +public partial class ValidationService(Config config) +{ + private readonly Config.LimitsConfig _limits = config.Limits; +} diff --git a/Foxnouns.Backend/Utils/AuthUtils.cs b/Foxnouns.Backend/Utils/AuthUtils.cs index 2ce46e2..d57eb73 100644 --- a/Foxnouns.Backend/Utils/AuthUtils.cs +++ b/Foxnouns.Backend/Utils/AuthUtils.cs @@ -135,7 +135,7 @@ public static class AuthUtils Convert.ToBase64String(RandomNumberGenerator.GetBytes(bytes)).Trim('='); public static string RandomToken(int bytes = 48) => - RandomUrlUnsafeToken() + RandomUrlUnsafeToken(bytes) // Make the token URL-safe .Replace('+', '-') .Replace('/', '_'); diff --git a/Foxnouns.Backend/Utils/Limits.cs b/Foxnouns.Backend/Utils/Limits.cs deleted file mode 100644 index 3010d46..0000000 --- a/Foxnouns.Backend/Utils/Limits.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -namespace Foxnouns.Backend.Utils; - -public static class Limits -{ - public const int FieldLimit = 25; - public const int FieldNameLimit = 100; - public const int FieldEntryTextLimit = 100; - public const int FieldEntriesLimit = 100; -} diff --git a/Foxnouns.Backend/Utils/OpenApi/PropertyKeySchemaTransformer.cs b/Foxnouns.Backend/Utils/OpenApi/PropertyKeySchemaTransformer.cs index 92c1f7c..9835b50 100644 --- a/Foxnouns.Backend/Utils/OpenApi/PropertyKeySchemaTransformer.cs +++ b/Foxnouns.Backend/Utils/OpenApi/PropertyKeySchemaTransformer.cs @@ -22,8 +22,10 @@ namespace Foxnouns.Backend.Utils.OpenApi; public class PropertyKeySchemaTransformer : IOpenApiSchemaTransformer { - private static readonly DefaultContractResolver SnakeCaseConverter = - new() { NamingStrategy = new SnakeCaseNamingStrategy() }; + private static readonly DefaultContractResolver SnakeCaseConverter = new() + { + NamingStrategy = new SnakeCaseNamingStrategy(), + }; public Task TransformAsync( OpenApiSchema schema, diff --git a/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs b/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs index ea12043..82ee485 100644 --- a/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs +++ b/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs @@ -12,189 +12,16 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -using System.Text.RegularExpressions; - namespace Foxnouns.Backend.Utils; public static partial class ValidationUtils { - private static readonly string[] InvalidUsernames = - [ - "..", - "admin", - "administrator", - "mod", - "moderator", - "api", - "page", - "pronouns", - "settings", - "pronouns.cc", - "pronounscc", - ]; + public const int MaximumReportContextLength = 512; - private static readonly string[] InvalidMemberNames = - [ - // these break routing outright - ".", - "..", - // the user edit page lives at `/@{username}/edit`, so a member named "edit" would be inaccessible - "edit", - ]; - - public static ValidationError? ValidateUsername(string username) - { - if (!UsernameRegex().IsMatch(username)) - { - return username.Length switch - { - < 2 => ValidationError.LengthError("Username is too short", 2, 40, username.Length), - > 40 => ValidationError.LengthError("Username is too long", 2, 40, username.Length), - _ => ValidationError.GenericValidationError( - "Username is invalid, can only contain alphanumeric characters, dashes, underscores, and periods", - username - ), - }; - } - - if ( - InvalidUsernames.Any(u => - string.Equals(u, username, StringComparison.InvariantCultureIgnoreCase) - ) - ) - { - return ValidationError.GenericValidationError("Username is not allowed", username); - } - - return null; - } - - public static ValidationError? ValidateMemberName(string memberName) - { - if (!MemberRegex().IsMatch(memberName)) - { - return memberName.Length switch - { - < 1 => ValidationError.LengthError("Name is too short", 1, 100, memberName.Length), - > 100 => ValidationError.LengthError("Name is too long", 1, 100, memberName.Length), - _ => ValidationError.GenericValidationError( - "Member name cannot contain any of the following: " - + " @, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), {, }, +, <, =, >, ^, |, ~, `, , " - + "and cannot be one or two periods", - memberName - ), - }; - } - - if ( - InvalidMemberNames.Any(u => - string.Equals(u, memberName, StringComparison.InvariantCultureIgnoreCase) - ) - ) - { - return ValidationError.GenericValidationError("Name is not allowed", memberName); - } - - return null; - } - - public static ValidationError? ValidateDisplayName(string? displayName) - { - return displayName?.Length switch - { - 0 => ValidationError.LengthError( - "Display name is too short", - 1, - 100, - displayName.Length - ), - > 100 => ValidationError.LengthError( - "Display name is too long", - 1, - 100, - displayName.Length - ), - _ => null, - }; - } - - private const int MaxLinks = 25; - private const int MaxLinkLength = 256; - - public static IEnumerable<(string, ValidationError?)> ValidateLinks(string[]? links) - { - if (links == null) - return []; - if (links.Length > MaxLinks) - { - return - [ - ("links", ValidationError.LengthError("Too many links", 0, MaxLinks, links.Length)), - ]; - } - - var errors = new List<(string, ValidationError?)>(); - foreach ((string link, int idx) in links.Select((l, i) => (l, i))) - { - switch (link.Length) - { - case 0: - errors.Add( - ( - $"links.{idx}", - ValidationError.LengthError("Link cannot be empty", 1, 256, 0) - ) - ); - break; - case > MaxLinkLength: - errors.Add( - ( - $"links.{idx}", - ValidationError.LengthError( - "Link is too long", - 1, - MaxLinkLength, - link.Length - ) - ) - ); - break; - } - } - - return errors; - } - - public const int MaxBioLength = 1024; - public const int MaxAvatarLength = 1_500_000; - - public static ValidationError? ValidateBio(string? bio) - { - return bio?.Length switch - { - 0 => ValidationError.LengthError("Bio is too short", 1, MaxBioLength, bio.Length), - > MaxBioLength => ValidationError.LengthError( - "Bio is too long", - 1, - MaxBioLength, - bio.Length - ), - _ => null, - }; - } - - public static ValidationError? ValidateAvatar(string? avatar) - { - return avatar?.Length switch - { - 0 => ValidationError.GenericValidationError("Avatar cannot be empty", null), - > MaxAvatarLength => ValidationError.GenericValidationError( - "Avatar is too large", - null - ), - _ => null, - }; - } + public static ValidationError? ValidateReportContext(string? context) => + context?.Length > MaximumReportContextLength + ? ValidationError.GenericValidationError("Report context is too long", null) + : null; public const int MinimumPasswordLength = 12; public const int MaximumPasswordLength = 1024; @@ -216,14 +43,4 @@ public static partial class ValidationUtils ), _ => null, }; - - [GeneratedRegex(@"^[a-zA-Z_0-9\-\.]{2,40}$", RegexOptions.IgnoreCase, "en-NL")] - private static partial Regex UsernameRegex(); - - [GeneratedRegex( - """^[^@'$%&()+<=>^|~`,*!#/\\\[\]""\{\}\?]{1,100}$""", - RegexOptions.IgnoreCase, - "en-NL" - )] - private static partial Regex MemberRegex(); } diff --git a/Foxnouns.Backend/config.example.ini b/Foxnouns.Backend/config.example.ini index 7522cba..4d7c17c 100644 --- a/Foxnouns.Backend/config.example.ini +++ b/Foxnouns.Backend/config.example.ini @@ -1,7 +1,7 @@ ; The host the server will listen on Host = localhost ; The port the server will listen on -Port = 5000 +Port = 6000 ; The base *external* URL BaseUrl = https://pronouns.localhost ; The base URL for media, without a trailing slash. This must be publicly accessible. @@ -43,6 +43,9 @@ AccessKey = SecretKey = Bucket = pronounscc +[Limits] +MaxMemberCount = 5000 + [EmailAuth] ; The address that emails will be sent from. If not set, email auth is disabled. From = noreply@accounts.pronouns.cc diff --git a/Foxnouns.Backend/packages.lock.json b/Foxnouns.Backend/packages.lock.json index 5f1b968..365ee8c 100644 --- a/Foxnouns.Backend/packages.lock.json +++ b/Foxnouns.Backend/packages.lock.json @@ -4,9 +4,9 @@ "net9.0": { "Coravel": { "type": "Direct", - "requested": "[6.0.0, )", - "resolved": "6.0.0", - "contentHash": "U16V/IxGL2TcpU9sT1gUA3pqoVIlz+WthC4idn8OTPiEtLElTcmNF6sHt+gOx8DRU8TBgN5vjfL4AHetjacOWQ==", + "requested": "[6.0.2, )", + "resolved": "6.0.2", + "contentHash": "/XZiRId4Ilar/OqjGKdxkZWfW97ekeT0wgiWNjGdqf8pPxiK508//Zkc0xrKMDOqchFT7B/oqAoQ+Vrx1txpPQ==", "dependencies": { "Microsoft.Extensions.Caching.Memory": "3.1.0", "Microsoft.Extensions.Configuration.Binder": "6.0.0", @@ -17,12 +17,12 @@ }, "Coravel.Mailer": { "type": "Direct", - "requested": "[7.0.0, )", - "resolved": "7.0.0", - "contentHash": "mxSlOOBxPjCAZruOpgXtubnZA9lD0DRgutApQmAsts7DoRfe0wTzqWrYjeZTiIzgVJZKZxJglN8duTvbPrw3jQ==", + "requested": "[7.1.0, )", + "resolved": "7.1.0", + "contentHash": "yMbUrwKl5/HbJeX8JkHa8Q3CPTJ3OmPyDSG7sULbXGEhzc2GiYIh7pmVhI1FFeL3VUtFavMDkS8PTwEeCpiwlg==", "dependencies": { - "MailKit": "4.3.0", - "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation": "6.0.27" + "MailKit": "4.8.0", + "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation": "6.0.36" } }, "EFCore.NamingConventions": { @@ -46,6 +46,37 @@ "Npgsql": "8.0.3" } }, + "Hangfire": { + "type": "Direct", + "requested": "[1.8.18, )", + "resolved": "1.8.18", + "contentHash": "EY+UqMHTOQAtdjeJf3jlnj8MpENyDPTpA6OHMncucVlkaongZjrx+gCN4bgma7vD3BNHqfQ7irYrfE5p1DOBEQ==", + "dependencies": { + "Hangfire.AspNetCore": "[1.8.18]", + "Hangfire.Core": "[1.8.18]", + "Hangfire.SqlServer": "[1.8.18]" + } + }, + "Hangfire.Core": { + "type": "Direct", + "requested": "[1.8.18, )", + "resolved": "1.8.18", + "contentHash": "oNAkV8QQoYg5+vM2M024NBk49EhTO2BmKDLuQaKNew23RpH9OUGtKDl1KldBdDJrD8TMFzjhWCArol3igd2i2w==", + "dependencies": { + "Newtonsoft.Json": "11.0.1" + } + }, + "Hangfire.Redis.StackExchange": { + "type": "Direct", + "requested": "[1.9.4, )", + "resolved": "1.9.4", + "contentHash": "rB4eGf4+hFhdnrN3//2O39JGuy1ThIKL3oTdVI2F3HqmSaSD9Cixl2xmMAqGJMld39Ke7eoP9sxbxnpVnYW66g==", + "dependencies": { + "Hangfire.Core": "1.8.7", + "Newtonsoft.Json": "13.0.3", + "StackExchange.Redis": "2.7.10" + } + }, "Humanizer.Core": { "type": "Direct", "requested": "[2.14.1, )", @@ -60,41 +91,41 @@ }, "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "pTFDEmZi3GheCSPrBxzyE63+d5unln2vYldo/nOm1xet/4rpEk2oJYcwpclPQ13E+LZBF9XixkgwYTUwqznlWg==", + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "cCnaxji6nqIHHLAEhZ6QirXCvwJNi0Q/qCPLkRW5SqMYNuOwoQdGk1KAhW65phBq1VHGt7wLbadpuGPGqfiZuA==", "dependencies": { - "Microsoft.AspNetCore.JsonPatch": "9.0.0", + "Microsoft.AspNetCore.JsonPatch": "9.0.2", "Newtonsoft.Json": "13.0.3", "Newtonsoft.Json.Bson": "1.0.2" } }, "Microsoft.AspNetCore.OpenApi": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "FqUK5j1EOPNuFT7IafltZQ3cakqhSwVzH5ZW1MhZDe4pPXs9sJ2M5jom1Omsu+mwF2tNKKlRAzLRHQTZzbd+6Q==", + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "JUndpjRNdG8GvzBLH/J4hen4ehWaPcshtiQ6+sUs1Bcj3a7dOsmWpDloDlpPeMOVSlhHwUJ3Xld0ClZjsFLgFQ==", "dependencies": { "Microsoft.OpenApi": "1.6.17" } }, "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "wpG+nfnfDAw87R3ovAsUmjr3MZ4tYXf6bFqEPVAIKE6IfPml3DS//iX0DBnf8kWn5ZHSO5oi1m4d/Jf+1LifJQ==", + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "P90ZuybgcpW32y985eOYxSoZ9IiL0UTYQlY0y1Pt1iHAnpZj/dQHREpSpry1RNvk8YjAeoAkWFdem5conqB9zQ==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "9.0.0", - "Microsoft.EntityFrameworkCore.Analyzers": "9.0.0", - "Microsoft.Extensions.Caching.Memory": "9.0.0", - "Microsoft.Extensions.Logging": "9.0.0" + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.2", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.2", + "Microsoft.Extensions.Caching.Memory": "9.0.2", + "Microsoft.Extensions.Logging": "9.0.2" } }, "Microsoft.EntityFrameworkCore.Design": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "Pqo8I+yHJ3VQrAoY0hiSncf+5P7gN/RkNilK5e+/K/yKh+yAWxdUAI6t0TG26a9VPlCa9FhyklzyFvRyj3YG9A==", + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "WWRmTxb/yd05cTW+k32lLvIhffxilgYvwKHDxiqe7GRLKeceyMspuf5BRpW65sFF7S2G+Be9JgjUe1ypGqt9tg==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.8.3", @@ -102,33 +133,45 @@ "Microsoft.CodeAnalysis.CSharp": "4.8.0", "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", - "Microsoft.EntityFrameworkCore.Relational": "9.0.0", - "Microsoft.Extensions.Caching.Memory": "9.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyModel": "9.0.0", - "Microsoft.Extensions.Logging": "9.0.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.2", + "Microsoft.Extensions.Caching.Memory": "9.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.DependencyModel": "9.0.2", + "Microsoft.Extensions.Logging": "9.0.2", "Mono.TextTemplating": "3.0.0", - "System.Text.Json": "9.0.0" + "System.Text.Json": "9.0.2" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "zbnPX/JQ0pETRSUG9fNPBvpIq42Aufvs15gGYyNIMhCun9yhmWihz0WgsI7bSDPjxWTKBf8oX/zv6v2uZ3W9OQ==", + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "AlEfp0DMz8E1h1Exi8LBrUCNmCYcGDfSM4F/uK1D1cYx/R3w0LVvlmjICqxqXTsy7BEZaCf5leRZY2FuPEiFaw==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0", - "Microsoft.Extensions.Primitives": "9.0.0" + "Microsoft.Extensions.Caching.Abstractions": "9.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2", + "Microsoft.Extensions.Primitives": "9.0.2" + } + }, + "Microsoft.Extensions.Http.Resilience": { + "type": "Direct", + "requested": "[9.2.0, )", + "resolved": "9.2.0", + "contentHash": "Km+YyCuk1IaeOsAzPDygtgsUOh3Fi89hpA18si0tFJmpSBf9aKzP9ffV5j7YOoVDvRWirpumXAPQzk1inBsvKw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "9.0.2", + "Microsoft.Extensions.Http.Diagnostics": "9.2.0", + "Microsoft.Extensions.ObjectPool": "9.0.2", + "Microsoft.Extensions.Resilience": "9.2.0" } }, "MimeKit": { "type": "Direct", - "requested": "[4.9.0, )", - "resolved": "4.9.0", - "contentHash": "DZXXMZzmAABDxFhOSMb6SE8KKxcRd/sk1E6aJTUE5ys2FWOQhznYV2Gl3klaaSfqKn27hQ32haqquH1J8Z6kJw==", + "requested": "[4.10.0, )", + "resolved": "4.10.0", + "contentHash": "GQofI17cH55XSh109hJmHaYMtSFqTX/eUek3UcV7hTnYayAIXZ6eHlv345tfdc+bQ/BrEnYOSZVzx9I3wpvvpg==", "dependencies": { "BouncyCastle.Cryptography": "2.5.0", "System.Formats.Asn1": "8.0.1", @@ -137,11 +180,11 @@ }, "Minio": { "type": "Direct", - "requested": "[6.0.3, )", - "resolved": "6.0.3", - "contentHash": "WHlkouclHtiK/pIXPHcjVmbeELHPtElj2qRSopFVpSmsFhZXeM10sPvczrkSPePsmwuvZdFryJ/hJzKu3XeLVg==", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "JckRL95hQ/eDHTQZ/BB7jeR0JyF+bOctMW6uriXHY5YPjCX61hiJGsswGjuDSEViKJEPxtPi3e4IwD/1TJ7PIw==", "dependencies": { - "CommunityToolkit.HighPerformance": "8.2.2", + "CommunityToolkit.HighPerformance": "8.3.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.Logging": "8.0.0", "System.IO.Hashing": "8.0.0", @@ -156,39 +199,39 @@ }, "NodaTime": { "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "yoRA3jEJn8NM0/rQm78zuDNPA3DonNSZdsorMUj+dltc1D+/Lc5h9YXGqbEEZozMGr37lAoYkcSM/KjTVqD0ow==" + "requested": "[3.2.1, )", + "resolved": "3.2.1", + "contentHash": "D1aHhUfPQUxU2nfDCVuSLahpp0xCYZTmj/KNH3mSK/tStJYcx9HO9aJ0qbOP3hzjGPV/DXOqY2AHe27Nt4xs4g==" }, "Npgsql.EntityFrameworkCore.PostgreSQL": { "type": "Direct", - "requested": "[9.0.2, )", - "resolved": "9.0.2", - "contentHash": "cYdOGplIvr9KgsG8nJ8xnzBTImeircbgetlzS1OmepS5dAQW6PuGpVrLOKBNEwEvGYZPsV8037X5vZ/Dmpwz7Q==", + "requested": "[9.0.4, )", + "resolved": "9.0.4", + "contentHash": "mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", "dependencies": { - "Microsoft.EntityFrameworkCore": "[9.0.0, 10.0.0)", - "Microsoft.EntityFrameworkCore.Relational": "[9.0.0, 10.0.0)", - "Npgsql": "9.0.2" + "Microsoft.EntityFrameworkCore": "[9.0.1, 10.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[9.0.1, 10.0.0)", + "Npgsql": "9.0.3" } }, "Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime": { "type": "Direct", - "requested": "[9.0.2, )", - "resolved": "9.0.2", - "contentHash": "+mfwiRCK+CAKTkeBZCuQuMaOwM/yMX8B65515PS1le9TUjlG8DobuAmb48MSR/Pr/YMvU1tV8FFEFlyQviQzrg==", + "requested": "[9.0.4, )", + "resolved": "9.0.4", + "contentHash": "QZ80CL3c9xzC83eVMWYWa1RcFZA6HJtpMAKFURlmz+1p0OyysSe8R6f/4sI9vk/nwqF6Fkw3lDgku/xH6HcJYg==", "dependencies": { - "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.2", - "Npgsql.NodaTime": "9.0.2" + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "Npgsql.NodaTime": "9.0.3" } }, "Npgsql.Json.NET": { "type": "Direct", - "requested": "[9.0.2, )", - "resolved": "9.0.2", - "contentHash": "E81dvvpNtS4WigxZu16OAFxVvPvbEkXI7vJXZzEp7GQ03MArF5V4HBb7KXDzTaE5ZQ0bhCUFoMTODC6Z8mu27g==", + "requested": "[9.0.3, )", + "resolved": "9.0.3", + "contentHash": "lN8p9UKkoXaGUhX3DHg/1W6YeEfbjQiQ7XrJSGREUoDHXOLxDQHJnZ49P/9P2s/pH6HTVgTgT5dijpKoRLN0vQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", - "Npgsql": "9.0.2" + "Npgsql": "9.0.3" } }, "prometheus-net": { @@ -212,24 +255,24 @@ }, "Roslynator.Analyzers": { "type": "Direct", - "requested": "[4.12.9, )", - "resolved": "4.12.9", - "contentHash": "X6lDpN/D5wuinq37KIx+l3GSUe9No+8bCjGBTI5sEEtxapLztkHg6gzNVhMXpXw8P+/5gFYxTXJ5Pf8O4iNz/w==" + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" }, "Scalar.AspNetCore": { "type": "Direct", - "requested": "[1.2.55, )", - "resolved": "1.2.55", - "contentHash": "zArlr6nfPQMRwyia0WFirsyczQby51GhNgWITiEIRkot+CVGZSGQ4oWGqExO11/6x26G+mcQo9Oft1mGpN0/ZQ==" + "requested": "[2.0.26, )", + "resolved": "2.0.26", + "contentHash": "0tKBFM7quBq0ifgRWo7eTTVpiTbnwpf/6ygtb/aYVuo0D2gMsYknAJRqEhH8HFBqzntNiYpzHbQSf2b+VAA8sA==" }, "Sentry.AspNetCore": { "type": "Direct", - "requested": "[4.13.0, )", - "resolved": "4.13.0", - "contentHash": "1cH9hSvjRbTkcpjUejFTrTC3jMIiOrcZ0DIvt16+AYqXhuxPEnI56npR1nhv+7WUGyhyp5cHFIZqrKnyrrGP0w==", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "zC2yhwQB0laYWGXLYDCsiKSIqleaEK3fUH9Z5t8Bgvfs2nGX0mHmh9oPqNAAbkVGvni56mhgHHCBxN/kpfkawA==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Sentry.Extensions.Logging": "4.13.0" + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Sentry.Extensions.Logging": "5.3.0" } }, "Serilog": { @@ -264,25 +307,35 @@ }, "Serilog.Sinks.Seq": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z5ig56/qzjkX6Fj4U/9m1g8HQaQiYPMZS4Uevtjg1I+WWzoGSf5t/E+6JbMP/jbZYhU63bA5NJN5y0x+qqx2Bw==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "aNU8A0K322q7+voPNmp1/qNPH+9QK8xvM1p72sMmCG0wGlshFzmtDW9QnVSoSYCj0MgQKcMOlgooovtBhRlNHw==", "dependencies": { - "Serilog": "4.0.0", - "Serilog.Sinks.File": "5.0.0" + "Serilog": "4.2.0", + "Serilog.Sinks.File": "6.0.0" } }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[3.1.6, )", - "resolved": "3.1.6", - "contentHash": "dHQ5jugF9v+5/LCVHCWVzaaIL6WOehqJy6eju/0VFYFPEj2WtqkGPoEV9EVQP83dHsdoqYaTuWpZdwAd37UwfA==" + "requested": "[3.1.7, )", + "resolved": "3.1.7", + "contentHash": "9fIOOAsyLFid6qKypM2Iy0Z3Q9yoanV8VoYAHtI2sYGMNKzhvRTjgFDHonIiVe+ANtxIxM6SuqUzj0r91nItpA==" + }, + "StackExchange.Redis": { + "type": "Direct", + "requested": "[2.8.31, )", + "resolved": "2.8.31", + "contentHash": "RCHVQa9Zke8k0oBgJn1Yl6BuYy8i6kv+sdMObiH60nOwD6QvWAjxdDwOm+LO78E8WsGiPqgOuItkz98fPS6haQ==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Pipelines.Sockets.Unofficial": "2.2.8" + } }, "System.Text.Json": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==" + "requested": "[9.0.2, )", + "resolved": "9.0.2", + "contentHash": "4TY2Yokh5Xp8XHFhsY9y84yokS7B0rhkaZCXuRiKppIiKwPVH4lVSFD9EEFzRpXdBM5ZeZXD43tc2vB6njEwwQ==" }, "System.Text.RegularExpressions": { "type": "Direct", @@ -293,6 +346,12 @@ "System.Runtime": "4.3.1" } }, + "Yort.Xid.Net": { + "type": "Direct", + "requested": "[2.0.1, )", + "resolved": "2.0.1", + "contentHash": "+3sNX7/RKSKheVuMz9jtWLazD+R4PXpx8va2d9SdDgvKOhETbEb0VYis8K/fD1qm/qOQT57LadToSpzReGMZlw==" + }, "BouncyCastle.Cryptography": { "type": "Transitive", "resolved": "2.5.0", @@ -300,8 +359,8 @@ }, "CommunityToolkit.HighPerformance": { "type": "Transitive", - "resolved": "8.2.2", - "contentHash": "+zIp8d3sbtYaRbM6hqDs4Ui/z34j7DcUmleruZlYLE4CVxXq+MO8XJyIs42vzeTYFX+k0Iq1dEbBUnQ4z/Gnrw==" + "resolved": "8.3.0", + "contentHash": "2zc0Wfr9OtEbLqm6J1Jycim/nKmYv+v12CytJ3tZGNzw7n3yjh1vNCMX0kIBaFBk3sw8g0pMR86QJGXGlArC+A==" }, "EntityFrameworkCore.Exceptions.Common": { "type": "Transitive", @@ -311,18 +370,46 @@ "Microsoft.EntityFrameworkCore.Relational": "8.0.0" } }, + "Hangfire.AspNetCore": { + "type": "Transitive", + "resolved": "1.8.18", + "contentHash": "5D6Do0qgoAnakvh4KnKwhIoUzFU84Z0sCYMB+Sit+ygkpL1P6JGYDcd/9vDBcfr5K3JqBxD4Zh2IK2LOXuuiaw==", + "dependencies": { + "Hangfire.NetCore": "[1.8.18]" + } + }, + "Hangfire.NetCore": { + "type": "Transitive", + "resolved": "1.8.18", + "contentHash": "3KAV9AZ1nqQHC54qR4buNEEKRmQJfq+lODtZxUk5cdi68lV8+9K2f4H1/mIfDlPpgjPFjEfCobNoi2+TIpKySw==", + "dependencies": { + "Hangfire.Core": "[1.8.18]", + "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "3.0.0", + "Microsoft.Extensions.Logging.Abstractions": "3.0.0" + } + }, + "Hangfire.SqlServer": { + "type": "Transitive", + "resolved": "1.8.18", + "contentHash": "yBfI2ygYfN/31rOrahfOFHee1mwTrG0ppsmK9awCS0mAr2GEaB9eyYqg/lURgZy8AA8UVJVs5nLHa2hc1pDAVQ==", + "dependencies": { + "Hangfire.Core": "[1.8.18]" + } + }, "MailKit": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "jVmB3Nr0JpqhyMiXOGWMin+QvRKpucGpSFBCav9dG6jEJPdBV+yp1RHVpKzxZPfT+0adaBuZlMFdbIciZo1EWA==", + "resolved": "4.8.0", + "contentHash": "zZ1UoM4FUnSFUJ9fTl5CEEaejR0DNP6+FDt1OfXnjg4igZntcir1tg/8Ufd6WY5vrpmvToAjluYqjVM24A+5lA==", "dependencies": { - "MimeKit": "4.3.0" + "MimeKit": "4.8.0", + "System.Formats.Asn1": "8.0.1" } }, "Microsoft.AspNetCore.JsonPatch": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "/4UONYoAIeexPoAmbzBPkVGA6KAY7t0BM+1sr0fKss2V1ERCdcM+Llub4X5Ma+LJ60oPp6KzM0e3j+Pp/JHCNw==", + "resolved": "9.0.2", + "contentHash": "bZMRhazEBgw9aZ5EBGYt0017CSd+aecsUCnppVjSa1SzWH6C1ieTSQZRAe+H0DzAVzWAoK7HLwKnQUPioopPrA==", "dependencies": { "Microsoft.CSharp": "4.7.0", "Newtonsoft.Json": "13.0.3" @@ -330,27 +417,27 @@ }, "Microsoft.AspNetCore.Mvc.Razor.Extensions": { "type": "Transitive", - "resolved": "6.0.27", - "contentHash": "trwJhFrTQuJTImmixMsDnDgRE8zuTzAUAot7WqiUlmjNzlJWLOaXXBpeA/xfNJvZuOsyGjC7RIzEyNyDGhDTLg==", + "resolved": "6.0.36", + "contentHash": "KFHRhrGAnd80310lpuWzI7Cf+GidS/h3JaPDFFnSmSGjCxB5vkBv5E+TXclJCJhqPtgNxg+keTC5SF1T9ieG5w==", "dependencies": { - "Microsoft.AspNetCore.Razor.Language": "6.0.27", - "Microsoft.CodeAnalysis.Razor": "6.0.27" + "Microsoft.AspNetCore.Razor.Language": "6.0.36", + "Microsoft.CodeAnalysis.Razor": "6.0.36" } }, "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation": { "type": "Transitive", - "resolved": "6.0.27", - "contentHash": "C6Gh/sAuUACxNtllcH4ZniWtPcGbixJuB1L5RXwoUe1a1wM6rpQ2TVMWpX2+cgeBj8U/izJyWY+nJ4Lz8mmMKA==", + "resolved": "6.0.36", + "contentHash": "0OG/wNedsQ9kTMrFuvrUDoJvp6Fxj6BzWgi7AUCluOENxu/0PzbjY9AC5w6mZJ22/AFxn2gFc2m0yOBTfQbiPg==", "dependencies": { - "Microsoft.AspNetCore.Mvc.Razor.Extensions": "6.0.27", - "Microsoft.CodeAnalysis.Razor": "6.0.27", - "Microsoft.Extensions.DependencyModel": "6.0.0" + "Microsoft.AspNetCore.Mvc.Razor.Extensions": "6.0.36", + "Microsoft.CodeAnalysis.Razor": "6.0.36", + "Microsoft.Extensions.DependencyModel": "6.0.2" } }, "Microsoft.AspNetCore.Razor.Language": { "type": "Transitive", - "resolved": "6.0.27", - "contentHash": "bI1kIZBgx7oJIB7utPrw4xIgcj7Pdx1jnHMTdsG54U602OcGpBzbfAuKaWs+LVdj+zZVuZsCSoRIZNJKTDP7Hw==" + "resolved": "6.0.36", + "contentHash": "n5Mg5D0aRrhHJJ6bJcwKqQydIFcgUq0jTlvuynoJjwA2IvAzh8Aqf9cpYagofQbIlIXILkCP6q6FgbngyVtpYA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", @@ -404,10 +491,10 @@ }, "Microsoft.CodeAnalysis.Razor": { "type": "Transitive", - "resolved": "6.0.27", - "contentHash": "NAUvSjH8QY8gPp/fXjHhi3MnQEGtSJA0iRT/dT3RKO3AdGACPJyGmKEKxLag9+Kf2On51yGHT9DEPPnK3hyezg==", + "resolved": "6.0.36", + "contentHash": "RTLNJglWezr/1IkiWdtDpPYW7X7lwa4ow8E35cHt+sWdWxOnl+ayQqMy1RfbaLp7CLmRmgXSzMMZZU3D4vZi9Q==", "dependencies": { - "Microsoft.AspNetCore.Razor.Language": "6.0.27", + "Microsoft.AspNetCore.Razor.Language": "6.0.36", "Microsoft.CodeAnalysis.CSharp": "4.0.0", "Microsoft.CodeAnalysis.Common": "4.0.0" } @@ -443,191 +530,274 @@ }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "fnmifFL8KaA4ZNLCVgfjCWhZUFxkrDInx5hR4qG7Q8IEaSiy/6VOSRFyx55oH7MV4y7wM3J3EE90nSpcVBI44Q==" + "resolved": "9.0.2", + "contentHash": "oVSjNSIYHsk0N66eqAWgDcyo9etEFbUswbz7SmlYR6nGp05byHrJAYM5N8U2aGWJWJI6WvIC2e4TXJgH6GZ6HQ==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "Qje+DzXJOKiXF72SL0XxNlDtTkvWWvmwknuZtFahY5hIQpRKO59qnGuERIQ3qlzuq5x4bAJ8WMbgU5DLhBgeOQ==" + "resolved": "9.0.2", + "contentHash": "w4jzX7XI+L3erVGzbHXpx64A3QaLXxqG3f1vPpGYYZGpxOIHkh7e4iLLD7cq4Ng1vjkwzWl5ZJp0Kj/nHsgFYg==" }, "Microsoft.EntityFrameworkCore.Relational": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "j+msw6fWgAE9M3Q/5B9Uhv7pdAdAQUvFPJAiBJmoy+OXvehVbfbCE8ftMAa51Uo2ZeiqVnHShhnv4Y4UJJmUzA==", + "resolved": "9.0.2", + "contentHash": "r7O4N5uaM95InVSGUj7SMOQWN0f1PBF2Y30ow7Jg+pGX5GJCRVd/1fq83lQ50YMyq+EzyHac5o4CDQA2RsjKJQ==", "dependencies": { - "Microsoft.EntityFrameworkCore": "9.0.0", - "Microsoft.Extensions.Caching.Memory": "9.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging": "9.0.0" + "Microsoft.EntityFrameworkCore": "9.0.2", + "Microsoft.Extensions.Caching.Memory": "9.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.Logging": "9.0.2" + } + }, + "Microsoft.Extensions.AmbientMetadata.Application": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "GMCX3zybUB22aAADjYPXrWhhd1HNMkcY5EcFAJnXy/4k5pPpJ6TS4VRl37xfrtosNyzbpO2SI7pd2Q5PvggSdg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.2", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2" } }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "FPWZAa9c0H4dvOj351iR1jkUIs4u9ykL4Bm592yhjDyO5lCoWd+TMAHx2EMbarzUvCvgjWjJIoC6//Q9kH6YhA==", + "resolved": "9.0.2", + "contentHash": "a7QhA25n+BzSM5r5d7JznfyluMBGI7z3qyLlFviZ1Eiqv6DdiK27sLZdP/rpYirBM6UYAKxu5TbmfhIy13GN9A==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.0" + "Microsoft.Extensions.Primitives": "9.0.2" + } + }, + "Microsoft.Extensions.Compliance.Abstractions": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "Te+N4xphDlGIS90lKJMZyezFiMWKLAtYV2/M8gGJG4thH6xyC7LWhMzgz2+tWMehxwZlBUq2D9DvVpjKBZFTPQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.ObjectPool": "9.0.2" } }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "resolved": "9.0.2", + "contentHash": "EBZW+u96tApIvNtjymXEIS44tH0I/jNwABHo4c33AchWOiDWCq2rL3klpnIo+xGrxoVGJzPDISV6hZ+a9C9SzQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.Primitives": "9.0.2" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "lqvd7W3FGKUO1+ZoUEMaZ5XDJeWvjpy2/M/ptCGz3tXLD4HWVaSzjufsAsjemasBEg+2SxXVtYVvGt5r2nKDlg==", + "resolved": "9.0.2", + "contentHash": "I0O/270E/lUNqbBxlRVjxKOMZyYjP88dpEgQTveml+h2lTzAP4vbawLVwjS9SC7lKaU893bwyyNz0IVJYsm9EA==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.0" + "Microsoft.Extensions.Primitives": "9.0.2" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "RiScL99DcyngY9zJA2ROrri7Br8tn5N4hP4YNvGdTN/bvg1A3dwvDOxHnNZ3Im7x2SJ5i4LkX1uPiR/MfSFBLQ==", + "resolved": "9.0.2", + "contentHash": "krJ04xR0aPXrOf5dkNASg6aJjsdzexvsMRL6UNOUjiTzqBvRr95sJ1owoKEm89bSONQCfZNhHrAFV9ahDqIPIw==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", + "resolved": "9.0.2", + "contentHash": "ZffbJrskOZ40JTzcTyKwFHS5eACSWp2bUQBBApIgGV+es8RaTD4OxUG7XxFr3RIPLXtYQ1jQzF2DjKB5fZn7Qg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" + "resolved": "9.0.2", + "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg==" + }, + "Microsoft.Extensions.DependencyInjection.AutoActivation": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "WcwfTpl3IcPcaahTVEaJwMUg1eWog1SkIA6jQZZFqMXiMX9/tVkhNB6yzUQmBdGWdlWDDRKpOmK7T7x1Uu05pQ==", + "dependencies": { + "Microsoft.Extensions.Hosting.Abstractions": "9.0.2" + } }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==" + "resolved": "9.0.2", + "contentHash": "3ImbcbS68jy9sKr9Z9ToRbEEX0bvIRdb8zyf5ebtL9Av2CUCGHvaO5wsSXfRfAjr60Vrq0tlmNji9IzAxW6EOw==" }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "9.0.2", + "contentHash": "kwFWk6DPaj1Roc0CExRv+TTwjsiERZA730jQIPlwCcS5tMaCAQtaGfwAK0z8CMFpVTiT+MgKXpd/P50qVCuIgg==", "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + "Microsoft.Extensions.Configuration": "9.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==", + "resolved": "9.0.2", + "contentHash": "kFwIZEC/37cwKuEm/nXvjF7A/Myz9O7c7P9Csgz6AOiiDE62zdOG5Bu7VkROu1oMYaX0wgijPJ5LqVt6+JKjVg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2" + } + }, + "Microsoft.Extensions.Diagnostics.ExceptionSummarization": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "et5JevHsLv1w1O1Zhb6LiUfai/nmDRzIHnbrZJdzLsIbbMCKTZpeHuANYIppAD//n12KvgOne05j4cu0GhG9gw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "uK439QzYR0q2emLVtYzwyK3x+T5bTY4yWsd/k/ZUS9LR6Sflp8MIdhGXW8kQCd86dQD4tLqvcbLkku8qHY263Q==", + "resolved": "9.0.2", + "contentHash": "IcOBmTlr2jySswU+3x8c3ql87FRwTVPQgVKaV5AXzPT5u0VItfNU8SMbESpdSp5STwxT/1R99WYszgHWsVkzhg==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.0" + "Microsoft.Extensions.Primitives": "9.0.2" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "yUKJgu81ExjvqbNWqZKshBbLntZMbMVz/P7Way2SBx7bMqA08Mfdc9O7hWDKAiSp+zPUGT6LKcSCQIPeDK+CCw==", + "resolved": "9.0.2", + "contentHash": "PvjZW6CMdZbPbOwKsQXYN5VPtIWZQqdTRuBPZiW3skhU3hymB17XSlLVC4uaBbDZU+/3eHG3p80y+MzZxZqR7Q==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.2", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.2", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2" } }, "Microsoft.Extensions.Http": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "resolved": "9.0.2", + "contentHash": "34+kcwxPZr3Owk9eZx268+gqGNB8G/8Y96gZHomxam0IOH08FhPBjPrLWDtKdVn4+sVUUJnJMpECSTJi4XXCcg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Diagnostics": "9.0.2", + "Microsoft.Extensions.Logging": "9.0.2", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2" + } + }, + "Microsoft.Extensions.Http.Diagnostics": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "Eeup1LuD5hVk5SsKAuX1D7I9sF380MjrNG10IaaauRLOmrRg8rq2TA8PYTXVBXf3MLkZ6m2xpBqRbZdxf8ygkg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.2.0", + "Microsoft.Extensions.Http": "9.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2", + "Microsoft.Extensions.Telemetry": "9.2.0", + "System.IO.Pipelines": "9.0.2" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", + "resolved": "9.0.2", + "contentHash": "loV/0UNpt2bD+6kCDzFALVE63CDtqzPeC0LAetkdhiEr/tTNbvOlQ7CBResH7BQBd3cikrwiBfaHdyHMFUlc2g==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0" + "Microsoft.Extensions.DependencyInjection": "9.0.2", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", + "resolved": "9.0.2", + "contentHash": "dV9s2Lamc8jSaqhl2BQSPn/AryDIH2sSbQUyLitLXV0ROmsb+SROnn2cH939JFbsNrnf3mIM3GNRKT7P0ldwLg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ixXXV0G/12g6MXK65TLngYN9V5hQQRuV+fZi882WIoVJT7h5JvoYoxTEwCgdqwLjSneqh1O+66gM8sMr9z/rsQ==", + "resolved": "9.0.2", + "contentHash": "pnwYZE7U6d3Y6iMVqADOAUUMMBGYAQPsT3fMwVr/V1Wdpe5DuVGFcViZavUthSJ5724NmelIl1cYy+kRfKfRPQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + "Microsoft.Extensions.Configuration": "9.0.2", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.Configuration.Binder": "9.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Logging": "9.0.2", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2" } }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "udvKco0sAVgYGTBnHUb0tY9JQzJ/nPDiv/8PIyz69wl1AibeCDZOLVVI+6156dPfHmJH7ws5oUJRiW4ZmAvuuA==" + "resolved": "9.0.2", + "contentHash": "nWx7uY6lfkmtpyC2dGc0IxtrZZs/LnLCQHw3YYQucbqWj8a27U/dZ+eh72O3ZiolqLzzLkVzoC+w/M8dZwxRTw==" }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", + "resolved": "9.0.2", + "contentHash": "zr98z+AN8+isdmDmQRuEJ/DAKZGUTHmdv3t0ZzjHvNqvA44nAgkXE9kYtfoN6581iALChhVaSw2Owt+Z2lVbkQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Primitives": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Primitives": "9.0.2" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "resolved": "9.0.2", + "contentHash": "OPm1NXdMg4Kb4Kz+YHdbBQfekh7MqQZ7liZ5dYUd+IbJakinv9Fl7Ck6Strbgs0a6E76UGbP/jHR532K/7/feQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.2", + "Microsoft.Extensions.Configuration.Binder": "9.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2", + "Microsoft.Extensions.Primitives": "9.0.2" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" + "resolved": "9.0.2", + "contentHash": "puBMtKe/wLuYa7H6docBkLlfec+h8L35DXqsDKKJgW0WY5oCwJ3cBJKcDaZchv6knAyqOMfsl6VUbaR++E5LXA==" + }, + "Microsoft.Extensions.Resilience": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "dyaM+Jeznh/i21bOrrRs3xceFfn0571EOjOq95dRXmL1rHDLC4ExhACJ2xipRBP6g1AgRNqmryi+hMrVWWgmlg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics": "9.0.2", + "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "9.2.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2", + "Microsoft.Extensions.Telemetry.Abstractions": "9.2.0", + "Polly.Extensions": "8.4.2", + "Polly.RateLimiting": "8.4.2" + } + }, + "Microsoft.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "4+bw7W4RrAMrND9TxonnSmzJOdXiPxljoda8OPJiReIN607mKCc0t0Mf28sHNsTujO1XQw28wsI0poxeeQxohw==", + "dependencies": { + "Microsoft.Extensions.AmbientMetadata.Application": "9.2.0", + "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.2.0", + "Microsoft.Extensions.Logging.Configuration": "9.0.2", + "Microsoft.Extensions.ObjectPool": "9.0.2", + "Microsoft.Extensions.Telemetry.Abstractions": "9.2.0" + } + }, + "Microsoft.Extensions.Telemetry.Abstractions": { + "type": "Transitive", + "resolved": "9.2.0", + "contentHash": "kEl+5G3RqS20XaEhHh/nOugcjKEK+rgVtMJra1iuwNzdzQXElelf3vu8TugcT7rIZ/T4T76EKW1OX/fmlxz4hw==", + "dependencies": { + "Microsoft.Extensions.Compliance.Abstractions": "9.2.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.2", + "Microsoft.Extensions.ObjectPool": "9.0.2", + "Microsoft.Extensions.Options": "9.0.2" + } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -662,35 +832,67 @@ }, "Npgsql": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "hCbO8box7i/XXiTFqCJ3GoowyLqx3JXxyrbOJ6om7dr+eAknvBNhhUHeJVGAQo44sySZTfdVffp4BrtPeLZOAA==", + "resolved": "9.0.3", + "contentHash": "tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Npgsql.NodaTime": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "jURb6VGmmR3pPae2N3HrUixSZ/U5ovqZgg/qo3m5Rq/q0m2fpxbZcsHZo21s5MLa/AfJAx4hcFMY98D4RtLdcg==", + "resolved": "9.0.3", + "contentHash": "PMWXCft/iw+5A7eCeMcy6YZXBst6oeisbCkv2JMQVG4SAFa5vQaf6K2voXzUJCqzwOFcCWs+oT42w2uMDFpchw==", "dependencies": { "NodaTime": "3.2.0", - "Npgsql": "9.0.2" + "Npgsql": "9.0.3" + } + }, + "Pipelines.Sockets.Unofficial": { + "type": "Transitive", + "resolved": "2.2.8", + "contentHash": "zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==", + "dependencies": { + "System.IO.Pipelines": "5.0.1" + } + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.4.2", + "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g==" + }, + "Polly.Extensions": { + "type": "Transitive", + "resolved": "8.4.2", + "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Polly.Core": "8.4.2" + } + }, + "Polly.RateLimiting": { + "type": "Transitive", + "resolved": "8.4.2", + "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==", + "dependencies": { + "Polly.Core": "8.4.2", + "System.Threading.RateLimiting": "8.0.0" } }, "Sentry": { "type": "Transitive", - "resolved": "4.13.0", - "contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg==" + "resolved": "5.3.0", + "contentHash": "zlBIP7YmYxySwcgapLMj1gdxPEz9rwdrOa4Yjub/TzcAaMQXusRH9hY4CE6pu0EIibZ7C7Hhjhr6xOTlyK8gFQ==" }, "Sentry.Extensions.Logging": { "type": "Transitive", - "resolved": "4.13.0", - "contentHash": "yZ5+TtJKWcss6cG17YjnovImx4X56T8O6Qy6bsMC8tMDttYy8J7HJ2F+WdaZNyjOCo0Rfi6N2gc+Clv/5pf+TQ==", + "resolved": "5.3.0", + "contentHash": "DPN6NXvO4LTH21UM2gUFJwSwVa/fuT3B/UZmQyfSfecqViXrZO7WFuKz/h592YUoGNCumyt8x045bxbz6j9btg==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.Http": "8.0.0", - "Microsoft.Extensions.Logging.Configuration": "8.0.0", - "Sentry": "4.13.0" + "Microsoft.Extensions.Configuration.Binder": "9.0.0", + "Microsoft.Extensions.Http": "9.0.0", + "Microsoft.Extensions.Logging.Configuration": "9.0.0", + "Sentry": "5.3.0" } }, "Serilog.Extensions.Hosting": { @@ -818,8 +1020,8 @@ }, "System.IO.Pipelines": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==" + "resolved": "9.0.2", + "contentHash": "UIBaK7c/A3FyQxmX/747xw4rCUkm1BhNiVU617U5jweNJssNjLJkPUGhBsrlDG0BpKWCYKsncD+Kqpy4KmvZZQ==" }, "System.Reactive": { "type": "Transitive", @@ -857,6 +1059,11 @@ "type": "Transitive", "resolved": "7.0.0", "contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==" + }, + "System.Threading.RateLimiting": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q==" } } } diff --git a/Foxnouns.Backend/static-pages/.gitignore b/Foxnouns.Backend/static-pages/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/Foxnouns.Backend/static-pages/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/Foxnouns.DataMigrator/Foxnouns.DataMigrator.csproj b/Foxnouns.DataMigrator/Foxnouns.DataMigrator.csproj index 5fde110..ead15ce 100644 --- a/Foxnouns.DataMigrator/Foxnouns.DataMigrator.csproj +++ b/Foxnouns.DataMigrator/Foxnouns.DataMigrator.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/Foxnouns.DataMigrator/UserMigrator.cs b/Foxnouns.DataMigrator/UserMigrator.cs index 0263c47..ee46878 100644 --- a/Foxnouns.DataMigrator/UserMigrator.cs +++ b/Foxnouns.DataMigrator/UserMigrator.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using Dapper; using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; -using Foxnouns.Backend.Utils; +using Foxnouns.Backend.Services; using Foxnouns.DataMigrator.Models; using NodaTime.Extensions; using Npgsql; @@ -39,6 +39,7 @@ public class UserMigrator( _user = new User { Id = goUser.SnowflakeId, + LegacyId = goUser.Id, Username = goUser.Username, DisplayName = goUser.DisplayName, Bio = goUser.Bio, @@ -139,6 +140,7 @@ public class UserMigrator( new PrideFlag { Id = flag.SnowflakeId, + LegacyId = flag.Id, UserId = _user!.Id, Hash = flag.Hash, Name = flag.Name, @@ -190,6 +192,7 @@ public class UserMigrator( UserId = _user!.Id, Name = goMember.Name, Sid = goMember.Sid, + LegacyId = goMember.Id, DisplayName = goMember.DisplayName, Bio = goMember.Bio, Avatar = goMember.Avatar, @@ -235,6 +238,7 @@ public class UserMigrator( "small" => PreferenceSize.Small, _ => PreferenceSize.Normal, }, + LegacyId = new Guid(id), }; } @@ -256,6 +260,6 @@ public class UserMigrator( { if (_preferenceIds.TryGetValue(id, out Snowflake preferenceId)) return preferenceId.ToString(); - return ValidationUtils.DefaultStatusOptions.Contains(id) ? id : "okay"; + return ValidationService.DefaultStatusOptions.Contains(id) ? id : "okay"; } } diff --git a/Foxnouns.Frontend/.env.example b/Foxnouns.Frontend/.env.example index d79c672..99c47b3 100644 --- a/Foxnouns.Frontend/.env.example +++ b/Foxnouns.Frontend/.env.example @@ -1,7 +1,18 @@ -# Example .env file--DO NOT EDIT +# Example .env file--DO NOT EDIT, copy to .env or .env.local then edit + +# The language the frontend will use. Valid languages are listed in src/lib/i18n/index.ts. PUBLIC_LANGUAGE=en +# The public base URL, i.e. the one users will see. Used for building links. PUBLIC_BASE_URL=https://pronouns.cc +# The base URL for the URL shortener service. Used for building short links. PUBLIC_SHORT_URL=https://prns.cc +# The base public URL for the API. This is (almost) always the public base URL + /api. PUBLIC_API_BASE=https://pronouns.cc/api +# The base *private* URL for the API's rate limiter proxy. The frontend will rewrite API URLs to use this. +# In development, you can set this to the same value as $PRIVATE_INTERNAL_API_HOST, but be aware that this will disable rate limiting. PRIVATE_API_HOST=http://localhost:5003/api -PRIVATE_INTERNAL_API_HOST=http://localhost:5000/api +# The base private URL for the API, which bypasses the rate limiter. Used for /api/internal paths and unauthenticated GET requests. +PRIVATE_INTERNAL_API_HOST=http://localhost:6000/api + +# The Sentry URL to use. Optional. +PRIVATE_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 diff --git a/Foxnouns.Frontend/.vscode/settings.json b/Foxnouns.Frontend/.vscode/settings.json index 5703e7f..c5d12a5 100644 --- a/Foxnouns.Frontend/.vscode/settings.json +++ b/Foxnouns.Frontend/.vscode/settings.json @@ -4,5 +4,6 @@ "i18n-ally.localesPaths": ["src/lib/i18n", "src/lib/i18n/locales"], "i18n-ally.keystyle": "nested", "explorer.sortOrder": "filesFirst", - "explorer.compactFolders": false + "explorer.compactFolders": false, + "eslint.validate": ["javascript", "javascriptreact", "svelte"] } diff --git a/Foxnouns.Frontend/Dockerfile b/Foxnouns.Frontend/Dockerfile index 166be23..2a86593 100644 --- a/Foxnouns.Frontend/Dockerfile +++ b/Foxnouns.Frontend/Dockerfile @@ -1,8 +1,4 @@ -FROM docker.io/node:22-slim - -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable +FROM docker.io/node:23-slim COPY ./Foxnouns.Frontend /app COPY ./docker/frontend.env /app/.env.local @@ -11,7 +7,7 @@ WORKDIR /app ENV PRIVATE_API_HOST=http://rate:5003/api ENV PRIVATE_INTERNAL_API_HOST=http://backend:5000/api -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile -RUN pnpm run build +RUN npm ci +RUN npm run build -CMD ["pnpm", "node", "build/index.js"] +CMD ["node", "build/index.js"] diff --git a/Foxnouns.Frontend/icons.js b/Foxnouns.Frontend/icons.js index 3efc0a4..0c9ffc4 100644 --- a/Foxnouns.Frontend/icons.js +++ b/Foxnouns.Frontend/icons.js @@ -1,6 +1,6 @@ // This script regenerates the list of icons for the frontend (Foxnouns.Frontend/src/lib/icons.ts) // and the backend (Foxnouns.Backend/Utils/BootstrapIcons.Icons.cs) from the currently installed version of Bootstrap Icons. -// Run with `pnpm node icons.js` in the frontend directory. +// Run with `node icons.js` in the frontend directory. import { writeFileSync } from "fs"; import icons from "bootstrap-icons/font/bootstrap-icons.json" with { type: "json" }; diff --git a/Foxnouns.Frontend/package-lock.json b/Foxnouns.Frontend/package-lock.json new file mode 100644 index 0000000..ac5f6f1 --- /dev/null +++ b/Foxnouns.Frontend/package-lock.json @@ -0,0 +1,6354 @@ +{ + "name": "foxnouns.frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "foxnouns.frontend", + "version": "0.0.1", + "dependencies": { + "@fontsource/firago": "^5.2.5", + "@sentry/sveltekit": "^9.11.0", + "base64-arraybuffer": "^1.0.2", + "bootstrap-icons": "^1.11.3", + "luxon": "^3.6.1", + "markdown-it": "^14.1.0", + "minidenticons": "^4.2.1", + "pretty-bytes": "^6.1.1", + "sanitize-html": "^2.15.0", + "svelte-tippy": "^1.3.2", + "tippy.js": "^6.3.7", + "tslog": "^4.9.3" + }, + "devDependencies": { + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.20.4", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@sveltestrap/sveltestrap": "^7.1.0", + "@types/eslint": "^9.6.1", + "@types/luxon": "^3.6.2", + "@types/markdown-it": "^14.1.2", + "@types/sanitize-html": "^2.15.0", + "bootstrap": "^5.3.5", + "dotenv": "^16.4.7", + "eslint": "^9.24.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.46.1", + "globals": "^16.0.0", + "prettier": "^3.5.3", + "prettier-plugin-svelte": "^3.3.3", + "sass": "^1.86.3", + "svelte": "^5.25.7", + "svelte-bootstrap-icons": "^3.1.2", + "svelte-check": "^4.1.5", + "svelte-easy-crop": "^4.0.1", + "sveltekit-i18n": "^2.4.2", + "typescript": "^5.8.3", + "typescript-eslint": "^8.29.0", + "vite": "^6.2.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fontsource/firago": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fontsource/firago/-/firago-5.2.5.tgz", + "integrity": "sha512-11vSR9Vyh0Tp/ChtheVSsK3yP9UGUUV5xJCdSOE8xNsQH/NZIGF36p0aeFTQ6uzBBaxaVjCGm0LEIFmxAwJoRw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", + "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz", + "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz", + "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz", + "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.44.2.tgz", + "integrity": "sha512-arSp97Y4D2NWogoXRb8CzFK3W2ooVdvqRRtQDljFt9uC3zI6OuShgey6CVFC0JxT1iGjkAr1r4PDz23mWrFULQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz", + "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz", + "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz", + "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz", + "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", + "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/semantic-conventions": "1.28.0", + "forwarded-parse": "2.1.2", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz", + "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz", + "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz", + "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz", + "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz", + "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz", + "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz", + "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz", + "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz", + "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz", + "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz", + "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz", + "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz", + "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.32.0.tgz", + "integrity": "sha512-s0OpmpQFSfMrmedAn9Lhg4KWJELHCU6uU9dtIJ28N8UGhf9Y55im5X8fEzwhwDwiSqN+ZPSNrDJF7ivf/AuRPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.5.0.tgz", + "integrity": "sha512-morJDtFRoAp5d/KENEm+K6Y3PQcn5bCvpJ5a9y3V3DNMrNy/ZSn2zulPGj+ld+Xj2UYVoaMJ8DpBX/o6iF6OiA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", + "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sentry-internal/browser-utils": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.13.0.tgz", + "integrity": "sha512-uZcbwcGI49oPC/YDEConJ+3xi2mu0TsVsDiMQKb6JoSc33KH37wq2IwXJb9nakzKJXxyMNemb44r8irAswjItw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.13.0.tgz", + "integrity": "sha512-fOhMnhEbOR5QVPtn5Gc5+UKQHjvAN/LmtYE6Qya3w2FDh3ZlnIXNFJWqwOneuICV3kCWjN4lLckwmzzwychr7A==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.13.0.tgz", + "integrity": "sha512-l+Atwab/bqI1N8+PSG1WWTCVmiOl7swL85Z9ntwS39QBnd66CTyzt/+j/n/UbAs8GienJK6FIfX1dvG1WmvUhA==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "9.13.0", + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.13.0.tgz", + "integrity": "sha512-5muW2BmEfWP1fpVWDNcIsph/WgqOqpHaXC1QMr4hk8/BWgt1/S2KPy85YiGVtM5lJJr0VhASKK8rBXG+9zm9IQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "9.13.0", + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.4.tgz", + "integrity": "sha512-yBzRn3GEUSv1RPtE4xB4LnuH74ZxtdoRJ5cmQ9i6mzlmGDxlrnKuvem5++AolZTE9oJqAD3Tx2rd1PqmpWnLoA==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@sentry/browser": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.13.0.tgz", + "integrity": "sha512-KiC8s9/6HvdlfCRqA420YbiBiXMBif7GYESJ8VQqOKUmlPczn8V2CRrEZjMqxhlHdIGiR0PS6jb2VSgeJBchJQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "9.13.0", + "@sentry-internal/feedback": "9.13.0", + "@sentry-internal/replay": "9.13.0", + "@sentry-internal/replay-canvas": "9.13.0", + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/bundler-plugin-core": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.2.4.tgz", + "integrity": "sha512-YMj9XW5W2JA89EeweE7CPKLDz245LBsI1JhCmqpt/bjSvmsSIAAPsLYnvIJBS3LQFm0OhtG8NB54PTi96dAcMA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/babel-plugin-component-annotate": "3.2.4", + "@sentry/cli": "2.42.2", + "dotenv": "^16.3.1", + "find-up": "^5.0.0", + "glob": "^9.3.2", + "magic-string": "0.30.8", + "unplugin": "1.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/cli": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.2.tgz", + "integrity": "sha512-spb7S/RUumCGyiSTg8DlrCX4bivCNmU/A1hcfkwuciTFGu8l5CDc2I6jJWWZw8/0enDGxuj5XujgXvU5tr4bxg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.42.2", + "@sentry/cli-linux-arm": "2.42.2", + "@sentry/cli-linux-arm64": "2.42.2", + "@sentry/cli-linux-i686": "2.42.2", + "@sentry/cli-linux-x64": "2.42.2", + "@sentry/cli-win32-i686": "2.42.2", + "@sentry/cli-win32-x64": "2.42.2" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz", + "integrity": "sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg==", + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz", + "integrity": "sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg==", + "cpu": [ + "arm" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz", + "integrity": "sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw==", + "cpu": [ + "arm64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz", + "integrity": "sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz", + "integrity": "sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz", + "integrity": "sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.42.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz", + "integrity": "sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cloudflare": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-9.13.0.tgz", + "integrity": "sha512-XaG/Kl5dSUJtzYkalQjaejGhrgFoj5w3cSWoXkxd8J+LXHsq7BFg4S0uCkzGJUmDHItlzfEY8BIaPpgPTJL7MQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.x" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/@sentry/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.13.0.tgz", + "integrity": "sha512-Zn1Qec5XNkNRE/M5QjL6YJLghETg6P188G/v2OzdHdHIRf0Y58/SnJilu3louF+ogos6kaSqqdMgzqKgZ8tCdg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.13.0.tgz", + "integrity": "sha512-75UVkrED5b0BaazNQKCmF8NqeqjErxildPojDyC037JN+cVFMPr/kFFGGm7E+eCvA/j2pAPUzqifHp/PjykPcw==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.2", + "@opentelemetry/instrumentation-amqplib": "^0.46.1", + "@opentelemetry/instrumentation-connect": "0.43.1", + "@opentelemetry/instrumentation-dataloader": "0.16.1", + "@opentelemetry/instrumentation-express": "0.47.1", + "@opentelemetry/instrumentation-fastify": "0.44.2", + "@opentelemetry/instrumentation-fs": "0.19.1", + "@opentelemetry/instrumentation-generic-pool": "0.43.1", + "@opentelemetry/instrumentation-graphql": "0.47.1", + "@opentelemetry/instrumentation-hapi": "0.45.2", + "@opentelemetry/instrumentation-http": "0.57.2", + "@opentelemetry/instrumentation-ioredis": "0.47.1", + "@opentelemetry/instrumentation-kafkajs": "0.7.1", + "@opentelemetry/instrumentation-knex": "0.44.1", + "@opentelemetry/instrumentation-koa": "0.47.1", + "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", + "@opentelemetry/instrumentation-mongodb": "0.52.0", + "@opentelemetry/instrumentation-mongoose": "0.46.1", + "@opentelemetry/instrumentation-mysql": "0.45.1", + "@opentelemetry/instrumentation-mysql2": "0.45.2", + "@opentelemetry/instrumentation-pg": "0.51.1", + "@opentelemetry/instrumentation-redis-4": "0.46.1", + "@opentelemetry/instrumentation-tedious": "0.18.1", + "@opentelemetry/instrumentation-undici": "0.10.1", + "@opentelemetry/resources": "^1.30.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.30.0", + "@prisma/instrumentation": "6.5.0", + "@sentry/core": "9.13.0", + "@sentry/opentelemetry": "9.13.0", + "import-in-the-middle": "^1.13.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.13.0.tgz", + "integrity": "sha512-TLSP0n+sXKVcVkAM2ttVmXcAT2K3e9D5gdPfr6aCnW+KIGJuD7wzla/TIcTWFaVwUejbvXAB6IFpZ/qA8HFwyA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.13.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.28.0" + } + }, + "node_modules/@sentry/svelte": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/svelte/-/svelte-9.13.0.tgz", + "integrity": "sha512-Sy8YOlIA0x4yhW4WM5ra2aarzKKrLgFTqkY6gAG3XrJ3DNFURrTDiaHUwQCICkLf2+zJKpuw6sfovBWqo1Z+DQ==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "9.13.0", + "@sentry/core": "9.13.0", + "magic-string": "^0.30.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "svelte": "3.x || 4.x || 5.x" + } + }, + "node_modules/@sentry/sveltekit": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@sentry/sveltekit/-/sveltekit-9.13.0.tgz", + "integrity": "sha512-U+uDKxAB+bI7nIiz/SfqPpoQMnenARXj0E3+z916bkgfdEyZUODkaQvHmmEKEOX7VRcRUT53mD/c1LwbxhSWxw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "7.26.9", + "@sentry/cloudflare": "9.13.0", + "@sentry/core": "9.13.0", + "@sentry/node": "9.13.0", + "@sentry/opentelemetry": "9.13.0", + "@sentry/svelte": "9.13.0", + "@sentry/vite-plugin": "3.2.4", + "magic-string": "0.30.7", + "recast": "0.23.11", + "sorcery": "1.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@sveltejs/kit": "2.x", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/@sentry/vite-plugin": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.2.4.tgz", + "integrity": "sha512-ZRn5TLlq5xtwKOqaWP+XqS1PYVfbBCgsbMk7wW2Ly6EgF9wYePvtLqKgYnE3hwPg2LpBnRPR2ti1ohlUkR+wXA==", + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugin-core": "3.2.4", + "unplugin": "1.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz", + "integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.20.7", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz", + "integrity": "sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "esm-env": "^1.2.2", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.0", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.15", + "vitefu": "^1.0.4" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@sveltekit-i18n/base": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@sveltekit-i18n/base/-/base-1.3.7.tgz", + "integrity": "sha512-kg1kql1/ro/lIudwFiWrv949Q07gmweln87tflUZR51MNdXXzK4fiJQv5Mw50K/CdQ5BOk/dJ0WOH2vOtBI6yw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": ">=3.49.0" + } + }, + "node_modules/@sveltekit-i18n/parser-default": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@sveltekit-i18n/parser-default/-/parser-default-1.1.1.tgz", + "integrity": "sha512-/gtzLlqm/sox7EoPKD56BxGZktK/syGc79EbJAPWY5KVitQD9SM0TP8yJCqDxTVPk7Lk0WJhrBGUE2Nn0f5M1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltestrap/sveltestrap": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-7.1.0.tgz", + "integrity": "sha512-TpIx25kqLV+z+VD3yfqYayOI1IaCeWFbT0uqM6NfA4vQgDs9PjFwmjkU4YEAlV/ngs9e7xPmaRWE7lkrg4Miow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.11.8" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/luxon": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz", + "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sanitize-html": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.15.0.tgz", + "integrity": "sha512-71Z6PbYsVKfp4i6Jvr37s5ql6if1Q/iJQT80NbaSi7uGaG8CqBMXP0pk/EsURAOuGdk5IJCd/vnzKrR7S3Txsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", + "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/type-utils": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", + "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", + "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", + "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", + "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", + "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", + "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", + "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bootstrap": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz", + "integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001714", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz", + "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", + "license": "ISC" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", + "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.35.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.43.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz", + "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "license": "MIT" + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "license": "MIT" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-in-the-middle": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", + "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/luxon": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minidenticons": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minidenticons/-/minidenticons-4.2.1.tgz", + "integrity": "sha512-oWfFivA0lOx/V/bO/YIJbthB26lV8JXYvhnv9zM2hNd3fzsHTXQ6c6bWZPcvhD3nnOB+lQk/D9lF43BXixrN8g==", + "license": "MIT", + "engines": { + "node": ">=15.14.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", + "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sanitize-html": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.16.0.tgz", + "integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sass": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz", + "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sorcery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-1.0.0.tgz", + "integrity": "sha512-5ay9oJE+7sNmhzl3YNG18jEEEf4AOQCM/FAqR5wMmzqd1FtRorFbJXn3w3SKOhbiQaVgHM+Q1lszZspjri7bpA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "minimist": "^1.2.0", + "tiny-glob": "^0.2.9" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.27.0.tgz", + "integrity": "sha512-Uai13Ydt1ZE+bUHme6b9U38PCYVNCqBRoBMkUKbFbKiD7kHWjdUUrklYAQZJxyKK81qII4mrBwe/YmvEMSlC9w==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-bootstrap-icons": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/svelte-bootstrap-icons/-/svelte-bootstrap-icons-3.1.2.tgz", + "integrity": "sha512-vy+qmWFfLJZxu5BaDlmaUG4uzki1rodX5ERZAP6KQdyO/2WNeGBDU4Yke3Z0NRq+VSepK86iAy+iUJvlUdsbBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/svelte-check": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.6.tgz", + "integrity": "sha512-P7w/6tdSfk3zEVvfsgrp3h3DFC75jCdZjTQvgGJtjPORs1n7/v2VMPIoty3PWv7jnfEm3x0G/p9wH4pecTb0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-easy-crop": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svelte-easy-crop/-/svelte-easy-crop-4.0.1.tgz", + "integrity": "sha512-0k7vVpHVLrPyobSXqey5IJUmFVxOoCaQrobFEsFXpSCyK8N5jTkRj1VX6NuCOZK8XXcMAqUvV0MktB8D5x1oCw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", + "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-tippy": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svelte-tippy/-/svelte-tippy-1.3.2.tgz", + "integrity": "sha512-41f+85hwhKBRqX0UNYrgFsi34Kk/KDvUkIZXYANxkWoA2NTVTCZbUC2J8hRNZ4TRVxObTshoZRjK2co5+i6LMw==", + "dependencies": { + "tippy.js": "6.3.7" + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/svelte/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/sveltekit-i18n": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/sveltekit-i18n/-/sveltekit-i18n-2.4.2.tgz", + "integrity": "sha512-hjRWn4V4DBL8JQKJoJa3MRvn6d32Zo+rWkoSP5bsQ/XIAguPdQUZJ8LMe6Nc1rST8WEVdu9+vZI3aFdKYGR3+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "./examples/*/" + ], + "dependencies": { + "@sveltekit-i18n/base": "~1.3.0", + "@sveltekit-i18n/parser-default": "~1.1.0" + }, + "peerDependencies": { + "svelte": ">=3.49.0" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tslog": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz", + "integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/fullstack-build/tslog?sponsor=1" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz", + "integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.30.1", + "@typescript-eslint/parser": "8.30.1", + "@typescript-eslint/utils": "8.30.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz", + "integrity": "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.8.1", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.5.0" + } + }, + "node_modules/unplugin/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/unplugin/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/unplugin/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.1.tgz", + "integrity": "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", + "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "license": "MIT" + } + } +} diff --git a/Foxnouns.Frontend/package.json b/Foxnouns.Frontend/package.json index b9e35fc..507e11f 100644 --- a/Foxnouns.Frontend/package.json +++ b/Foxnouns.Frontend/package.json @@ -12,40 +12,42 @@ "lint": "prettier --check . && eslint ." }, "devDependencies": { - "@sveltejs/adapter-node": "^5.2.10", - "@sveltejs/kit": "^2.11.1", - "@sveltejs/vite-plugin-svelte": "^4.0.3", - "@sveltestrap/sveltestrap": "^6.2.7", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.20.4", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@sveltestrap/sveltestrap": "^7.1.0", "@types/eslint": "^9.6.1", - "@types/luxon": "^3.4.2", + "@types/luxon": "^3.6.2", "@types/markdown-it": "^14.1.2", - "@types/sanitize-html": "^2.13.0", - "bootstrap": "^5.3.3", - "eslint": "^9.17.0", + "@types/sanitize-html": "^2.15.0", + "bootstrap": "^5.3.5", + "dotenv": "^16.4.7", + "eslint": "^9.24.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.46.1", - "globals": "^15.13.0", - "prettier": "^3.4.2", - "prettier-plugin-svelte": "^3.3.2", - "sass": "^1.83.0", - "svelte": "^5.13.0", - "svelte-bootstrap-icons": "^3.1.1", - "svelte-check": "^4.1.1", + "globals": "^16.0.0", + "prettier": "^3.5.3", + "prettier-plugin-svelte": "^3.3.3", + "sass": "^1.86.3", + "svelte": "^5.25.7", + "svelte-bootstrap-icons": "^3.1.2", + "svelte-check": "^4.1.5", + "svelte-easy-crop": "^4.0.1", "sveltekit-i18n": "^2.4.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0", - "vite": "^5.4.11" + "typescript": "^5.8.3", + "typescript-eslint": "^8.29.0", + "vite": "^6.2.5" }, - "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c", "dependencies": { - "@fontsource/firago": "^5.1.0", + "@fontsource/firago": "^5.2.5", + "@sentry/sveltekit": "^9.11.0", "base64-arraybuffer": "^1.0.2", "bootstrap-icons": "^1.11.3", - "luxon": "^3.5.0", + "luxon": "^3.6.1", "markdown-it": "^14.1.0", "minidenticons": "^4.2.1", "pretty-bytes": "^6.1.1", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.15.0", "svelte-tippy": "^1.3.2", "tippy.js": "^6.3.7", "tslog": "^4.9.3" diff --git a/Foxnouns.Frontend/pnpm-lock.yaml b/Foxnouns.Frontend/pnpm-lock.yaml deleted file mode 100644 index 2ebd886..0000000 --- a/Foxnouns.Frontend/pnpm-lock.yaml +++ /dev/null @@ -1,2686 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@fontsource/firago': - specifier: ^5.1.0 - version: 5.1.0 - base64-arraybuffer: - specifier: ^1.0.2 - version: 1.0.2 - bootstrap-icons: - specifier: ^1.11.3 - version: 1.11.3 - luxon: - specifier: ^3.5.0 - version: 3.5.0 - markdown-it: - specifier: ^14.1.0 - version: 14.1.0 - minidenticons: - specifier: ^4.2.1 - version: 4.2.1 - pretty-bytes: - specifier: ^6.1.1 - version: 6.1.1 - sanitize-html: - specifier: ^2.13.1 - version: 2.13.1 - svelte-tippy: - specifier: ^1.3.2 - version: 1.3.2 - tippy.js: - specifier: ^6.3.7 - version: 6.3.7 - tslog: - specifier: ^4.9.3 - version: 4.9.3 - devDependencies: - '@sveltejs/adapter-node': - specifier: ^5.2.10 - version: 5.2.10(@sveltejs/kit@2.11.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0))) - '@sveltejs/kit': - specifier: ^2.11.1 - version: 2.11.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - '@sveltejs/vite-plugin-svelte': - specifier: ^4.0.3 - version: 4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - '@sveltestrap/sveltestrap': - specifier: ^6.2.7 - version: 6.2.7(svelte@5.13.0) - '@types/eslint': - specifier: ^9.6.1 - version: 9.6.1 - '@types/luxon': - specifier: ^3.4.2 - version: 3.4.2 - '@types/markdown-it': - specifier: ^14.1.2 - version: 14.1.2 - '@types/sanitize-html': - specifier: ^2.13.0 - version: 2.13.0 - bootstrap: - specifier: ^5.3.3 - version: 5.3.3(@popperjs/core@2.11.8) - eslint: - specifier: ^9.17.0 - version: 9.17.0 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.17.0) - eslint-plugin-svelte: - specifier: ^2.46.1 - version: 2.46.1(eslint@9.17.0)(svelte@5.13.0) - globals: - specifier: ^15.13.0 - version: 15.13.0 - prettier: - specifier: ^3.4.2 - version: 3.4.2 - prettier-plugin-svelte: - specifier: ^3.3.2 - version: 3.3.2(prettier@3.4.2)(svelte@5.13.0) - sass: - specifier: ^1.83.0 - version: 1.83.0 - svelte: - specifier: ^5.13.0 - version: 5.13.0 - svelte-bootstrap-icons: - specifier: ^3.1.1 - version: 3.1.1 - svelte-check: - specifier: ^4.1.1 - version: 4.1.1(picomatch@4.0.2)(svelte@5.13.0)(typescript@5.7.2) - sveltekit-i18n: - specifier: ^2.4.2 - version: 2.4.2(svelte@5.13.0) - typescript: - specifier: ^5.7.2 - version: 5.7.2 - typescript-eslint: - specifier: ^8.18.0 - version: 8.18.0(eslint@9.17.0)(typescript@5.7.2) - vite: - specifier: ^5.4.11 - version: 5.4.11(sass@1.83.0) - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.17.0': - resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@fontsource/firago@5.1.0': - resolution: {integrity: sha512-ym1zCs5Zmp7J6vJ2AzU74H008bxebz53OBY2yPe+qD7+UXwu1S4bHpHYBbznu+HMxccW+JkEZg6UTyqc90V2ug==} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} - engines: {node: '>=18.18'} - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@parcel/watcher-android-arm64@2.5.0': - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.0': - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.0': - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.0': - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.0': - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm-musl@2.5.0': - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm64-glibc@2.5.0': - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-arm64-musl@2.5.0': - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-x64-glibc@2.5.0': - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-linux-x64-musl@2.5.0': - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-win32-arm64@2.5.0': - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.0': - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.0': - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.0': - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} - engines: {node: '>= 10.0.0'} - - '@polka/url@1.0.0-next.28': - resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - - '@rollup/plugin-commonjs@28.0.1': - resolution: {integrity: sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==} - engines: {node: '>=16.0.0 || 14 >= 14.17'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-json@6.1.0': - resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-node-resolve@15.3.0': - resolution: {integrity: sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/pluginutils@5.1.3': - resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.28.1': - resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.28.1': - resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.28.1': - resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.28.1': - resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.28.1': - resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.28.1': - resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': - resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.28.1': - resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.28.1': - resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.28.1': - resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.28.1': - resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': - resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.28.1': - resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.28.1': - resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.28.1': - resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.28.1': - resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.28.1': - resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.28.1': - resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.28.1': - resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} - cpu: [x64] - os: [win32] - - '@sveltejs/adapter-node@5.2.10': - resolution: {integrity: sha512-U0SCdULhHbSYCDgvvrHRtKUykl9GZkM/b3NyeIUtaxM39upQFd5059pWmXgTNaNTU1HMdj4xx0xvNAvQcIzmXQ==} - peerDependencies: - '@sveltejs/kit': ^2.4.0 - - '@sveltejs/kit@2.11.1': - resolution: {integrity: sha512-dAiHDEd+AOm20eYdMPV1a2eKBOc0s/7XsSs7PCoNv2kKS7BAoVRC9uzR+FQmxLtp8xuEo9z8CtrMQoszkThltQ==} - engines: {node: '>=18.13'} - hasBin: true - peerDependencies: - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 - svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 || ^6.0.0 - - '@sveltejs/vite-plugin-svelte-inspector@3.0.1': - resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} - peerDependencies: - '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 - svelte: ^5.0.0-next.96 || ^5.0.0 - vite: ^5.0.0 - - '@sveltejs/vite-plugin-svelte@4.0.3': - resolution: {integrity: sha512-J7nC5gT5qpmvyD2pmzPUntLUgoinyEaNy9sTpGGE6N7pblggO0A1NyneJJvR2ELlzK6ti28aF2SLXG1yJdnJeA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} - peerDependencies: - svelte: ^5.0.0-next.96 || ^5.0.0 - vite: ^5.0.0 - - '@sveltekit-i18n/base@1.3.7': - resolution: {integrity: sha512-kg1kql1/ro/lIudwFiWrv949Q07gmweln87tflUZR51MNdXXzK4fiJQv5Mw50K/CdQ5BOk/dJ0WOH2vOtBI6yw==} - peerDependencies: - svelte: '>=3.49.0' - - '@sveltekit-i18n/parser-default@1.1.1': - resolution: {integrity: sha512-/gtzLlqm/sox7EoPKD56BxGZktK/syGc79EbJAPWY5KVitQD9SM0TP8yJCqDxTVPk7Lk0WJhrBGUE2Nn0f5M1w==} - - '@sveltestrap/sveltestrap@6.2.7': - resolution: {integrity: sha512-WwLLfAFUb42BGuRrf3Vbct30bQMzlEMMipN/MfxhjuLTmLQeW9muVJfPyvjtWS+mY+RjkSCoHvAp/ZobP1NLlQ==} - peerDependencies: - svelte: ^4.0.0 || ^5.0.0 || ^5.0.0-next.0 - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/linkify-it@5.0.0': - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - - '@types/luxon@3.4.2': - resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - - '@types/markdown-it@14.1.2': - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - - '@types/mdurl@2.0.0': - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - - '@types/sanitize-html@2.13.0': - resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==} - - '@typescript-eslint/eslint-plugin@8.18.0': - resolution: {integrity: sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/parser@8.18.0': - resolution: {integrity: sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/scope-manager@8.18.0': - resolution: {integrity: sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.18.0': - resolution: {integrity: sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/types@8.18.0': - resolution: {integrity: sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.18.0': - resolution: {integrity: sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/utils@8.18.0': - resolution: {integrity: sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - '@typescript-eslint/visitor-keys@8.18.0': - resolution: {integrity: sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-typescript@1.4.13: - resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} - peerDependencies: - acorn: '>=8.9.0' - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-arraybuffer@1.0.2: - resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} - engines: {node: '>= 0.6.0'} - - bootstrap-icons@1.11.3: - resolution: {integrity: sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==} - - bootstrap@5.3.3: - resolution: {integrity: sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==} - peerDependencies: - '@popperjs/core': ^2.11.8 - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chokidar@4.0.1: - resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} - engines: {node: '>= 14.16.0'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - - devalue@5.1.1: - resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} - - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-compat-utils@0.5.1: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-svelte@2.46.1: - resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 - svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - svelte: - optional: true - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.17.0: - resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - esm-env@1.2.1: - resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} - - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrap@1.2.3: - resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fdir@6.4.2: - resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@15.13.0: - resolution: {integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==} - engines: {node: '>=18'} - - globalyzer@0.1.0: - resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - htmlparser2@8.0.2: - resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - immutable@5.0.3: - resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - import-meta-resolve@4.1.0: - resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - is-core-module@2.16.0: - resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - known-css-properties@0.35.0: - resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - - locate-character@3.0.0: - resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - luxon@3.5.0: - resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} - engines: {node: '>=12'} - - magic-string@0.30.15: - resolution: {integrity: sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==} - - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} - hasBin: true - - mdurl@2.0.0: - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - minidenticons@4.2.1: - resolution: {integrity: sha512-oWfFivA0lOx/V/bO/YIJbthB26lV8JXYvhnv9zM2hNd3fzsHTXQ6c6bWZPcvhD3nnOB+lQk/D9lF43BXixrN8g==} - engines: {node: '>=15.14.0'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-srcset@1.0.2: - resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-safe-parser@6.0.0: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.3.3 - - postcss-scss@4.0.9: - resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.4.29 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-plugin-svelte@3.3.2: - resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==} - peerDependencies: - prettier: ^3.0.0 - svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - - prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} - engines: {node: '>=14'} - hasBin: true - - pretty-bytes@6.1.1: - resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} - engines: {node: ^14.13.1 || >=16.0.0} - - punycode.js@2.3.1: - resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} - engines: {node: '>=6'} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - readdirp@4.0.2: - resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} - engines: {node: '>= 14.16.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve@1.22.9: - resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==} - hasBin: true - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup@4.28.1: - resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - - sanitize-html@2.13.1: - resolution: {integrity: sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==} - - sass@1.83.0: - resolution: {integrity: sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==} - engines: {node: '>=14.0.0'} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - sirv@3.0.0: - resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svelte-bootstrap-icons@3.1.1: - resolution: {integrity: sha512-ghJlt6TX3IX35M7wSvGyrmVgXeT5GMRF+7+q6L4OUT2RJWF09mQIvZTZ04Ii3FBfg10KdzFdvVuoB8M0cVHfzw==} - - svelte-check@4.1.1: - resolution: {integrity: sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==} - engines: {node: '>= 18.0.0'} - hasBin: true - peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: '>=5.0.0' - - svelte-eslint-parser@0.43.0: - resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - svelte: - optional: true - - svelte-tippy@1.3.2: - resolution: {integrity: sha512-41f+85hwhKBRqX0UNYrgFsi34Kk/KDvUkIZXYANxkWoA2NTVTCZbUC2J8hRNZ4TRVxObTshoZRjK2co5+i6LMw==} - - svelte@5.13.0: - resolution: {integrity: sha512-ZG4VmBNze/j2KxT2GEeUm8Jr3RLYQ3P5Y9/flUDCgaAxgzx4ZRTdiyh+PCr7qRlOr5M8uidIqr+3DwUFVrdL+A==} - engines: {node: '>=18'} - - sveltekit-i18n@2.4.2: - resolution: {integrity: sha512-hjRWn4V4DBL8JQKJoJa3MRvn6d32Zo+rWkoSP5bsQ/XIAguPdQUZJ8LMe6Nc1rST8WEVdu9+vZI3aFdKYGR3+Q==} - peerDependencies: - svelte: '>=3.49.0' - - tiny-glob@0.2.9: - resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} - - tippy.js@6.3.7: - resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - tslog@4.9.3: - resolution: {integrity: sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==} - engines: {node: '>=16'} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - typescript-eslint@8.18.0: - resolution: {integrity: sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} - engines: {node: '>=14.17'} - hasBin: true - - uc.micro@2.1.0: - resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vitefu@1.0.4: - resolution: {integrity: sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - vite: - optional: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zimmerframe@1.1.2: - resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - -snapshots: - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0)': - dependencies: - eslint: 9.17.0 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/config-array@0.19.1': - dependencies: - '@eslint/object-schema': 2.1.5 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/core@0.9.1': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.2.0': - dependencies: - ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.17.0': {} - - '@eslint/object-schema@2.1.5': {} - - '@eslint/plugin-kit@0.2.4': - dependencies: - levn: 0.4.1 - - '@fontsource/firago@5.1.0': {} - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.1': {} - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@parcel/watcher-android-arm64@2.5.0': - optional: true - - '@parcel/watcher-darwin-arm64@2.5.0': - optional: true - - '@parcel/watcher-darwin-x64@2.5.0': - optional: true - - '@parcel/watcher-freebsd-x64@2.5.0': - optional: true - - '@parcel/watcher-linux-arm-glibc@2.5.0': - optional: true - - '@parcel/watcher-linux-arm-musl@2.5.0': - optional: true - - '@parcel/watcher-linux-arm64-glibc@2.5.0': - optional: true - - '@parcel/watcher-linux-arm64-musl@2.5.0': - optional: true - - '@parcel/watcher-linux-x64-glibc@2.5.0': - optional: true - - '@parcel/watcher-linux-x64-musl@2.5.0': - optional: true - - '@parcel/watcher-win32-arm64@2.5.0': - optional: true - - '@parcel/watcher-win32-ia32@2.5.0': - optional: true - - '@parcel/watcher-win32-x64@2.5.0': - optional: true - - '@parcel/watcher@2.5.0': - dependencies: - detect-libc: 1.0.3 - is-glob: 4.0.3 - micromatch: 4.0.8 - node-addon-api: 7.1.1 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.0 - '@parcel/watcher-darwin-arm64': 2.5.0 - '@parcel/watcher-darwin-x64': 2.5.0 - '@parcel/watcher-freebsd-x64': 2.5.0 - '@parcel/watcher-linux-arm-glibc': 2.5.0 - '@parcel/watcher-linux-arm-musl': 2.5.0 - '@parcel/watcher-linux-arm64-glibc': 2.5.0 - '@parcel/watcher-linux-arm64-musl': 2.5.0 - '@parcel/watcher-linux-x64-glibc': 2.5.0 - '@parcel/watcher-linux-x64-musl': 2.5.0 - '@parcel/watcher-win32-arm64': 2.5.0 - '@parcel/watcher-win32-ia32': 2.5.0 - '@parcel/watcher-win32-x64': 2.5.0 - optional: true - - '@polka/url@1.0.0-next.28': {} - - '@popperjs/core@2.11.8': {} - - '@rollup/plugin-commonjs@28.0.1(rollup@4.28.1)': - dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.28.1) - commondir: 1.0.1 - estree-walker: 2.0.2 - fdir: 6.4.2(picomatch@4.0.2) - is-reference: 1.2.1 - magic-string: 0.30.15 - picomatch: 4.0.2 - optionalDependencies: - rollup: 4.28.1 - - '@rollup/plugin-json@6.1.0(rollup@4.28.1)': - dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.28.1) - optionalDependencies: - rollup: 4.28.1 - - '@rollup/plugin-node-resolve@15.3.0(rollup@4.28.1)': - dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.28.1) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.9 - optionalDependencies: - rollup: 4.28.1 - - '@rollup/pluginutils@5.1.3(rollup@4.28.1)': - dependencies: - '@types/estree': 1.0.6 - estree-walker: 2.0.2 - picomatch: 4.0.2 - optionalDependencies: - rollup: 4.28.1 - - '@rollup/rollup-android-arm-eabi@4.28.1': - optional: true - - '@rollup/rollup-android-arm64@4.28.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.28.1': - optional: true - - '@rollup/rollup-darwin-x64@4.28.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.28.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.28.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.28.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.28.1': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.28.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.28.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.28.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.28.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.28.1': - optional: true - - '@sveltejs/adapter-node@5.2.10(@sveltejs/kit@2.11.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))': - dependencies: - '@rollup/plugin-commonjs': 28.0.1(rollup@4.28.1) - '@rollup/plugin-json': 6.1.0(rollup@4.28.1) - '@rollup/plugin-node-resolve': 15.3.0(rollup@4.28.1) - '@sveltejs/kit': 2.11.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - rollup: 4.28.1 - - '@sveltejs/kit@2.11.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0))': - dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - '@types/cookie': 0.6.0 - cookie: 0.6.0 - devalue: 5.1.1 - esm-env: 1.2.1 - import-meta-resolve: 4.1.0 - kleur: 4.1.5 - magic-string: 0.30.15 - mrmime: 2.0.0 - sade: 1.8.1 - set-cookie-parser: 2.7.1 - sirv: 3.0.0 - svelte: 5.13.0 - tiny-glob: 0.2.9 - vite: 5.4.11(sass@1.83.0) - - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0))': - dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - debug: 4.4.0 - svelte: 5.13.0 - vite: 5.4.11(sass@1.83.0) - transitivePeerDependencies: - - supports-color - - '@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0))': - dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.3(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)))(svelte@5.13.0)(vite@5.4.11(sass@1.83.0)) - debug: 4.4.0 - deepmerge: 4.3.1 - kleur: 4.1.5 - magic-string: 0.30.15 - svelte: 5.13.0 - vite: 5.4.11(sass@1.83.0) - vitefu: 1.0.4(vite@5.4.11(sass@1.83.0)) - transitivePeerDependencies: - - supports-color - - '@sveltekit-i18n/base@1.3.7(svelte@5.13.0)': - dependencies: - svelte: 5.13.0 - - '@sveltekit-i18n/parser-default@1.1.1': {} - - '@sveltestrap/sveltestrap@6.2.7(svelte@5.13.0)': - dependencies: - '@popperjs/core': 2.11.8 - svelte: 5.13.0 - - '@types/cookie@0.6.0': {} - - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 - - '@types/estree@1.0.6': {} - - '@types/json-schema@7.0.15': {} - - '@types/linkify-it@5.0.0': {} - - '@types/luxon@3.4.2': {} - - '@types/markdown-it@14.1.2': - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 - - '@types/mdurl@2.0.0': {} - - '@types/resolve@1.20.2': {} - - '@types/sanitize-html@2.13.0': - dependencies: - htmlparser2: 8.0.2 - - '@typescript-eslint/eslint-plugin@8.18.0(@typescript-eslint/parser@8.18.0(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.18.0 - '@typescript-eslint/type-utils': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.18.0 - eslint: 9.17.0 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.18.0(eslint@9.17.0)(typescript@5.7.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.18.0 - '@typescript-eslint/types': 8.18.0 - '@typescript-eslint/typescript-estree': 8.18.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.18.0 - debug: 4.4.0 - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.18.0': - dependencies: - '@typescript-eslint/types': 8.18.0 - '@typescript-eslint/visitor-keys': 8.18.0 - - '@typescript-eslint/type-utils@8.18.0(eslint@9.17.0)(typescript@5.7.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.18.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - debug: 4.4.0 - eslint: 9.17.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.18.0': {} - - '@typescript-eslint/typescript-estree@8.18.0(typescript@5.7.2)': - dependencies: - '@typescript-eslint/types': 8.18.0 - '@typescript-eslint/visitor-keys': 8.18.0 - debug: 4.4.0 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.18.0(eslint@9.17.0)(typescript@5.7.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@typescript-eslint/scope-manager': 8.18.0 - '@typescript-eslint/types': 8.18.0 - '@typescript-eslint/typescript-estree': 8.18.0(typescript@5.7.2) - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.18.0': - dependencies: - '@typescript-eslint/types': 8.18.0 - eslint-visitor-keys: 4.2.0 - - acorn-jsx@5.3.2(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn-typescript@1.4.13(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - argparse@2.0.1: {} - - aria-query@5.3.2: {} - - axobject-query@4.1.0: {} - - balanced-match@1.0.2: {} - - base64-arraybuffer@1.0.2: {} - - bootstrap-icons@1.11.3: {} - - bootstrap@5.3.3(@popperjs/core@2.11.8): - dependencies: - '@popperjs/core': 2.11.8 - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - callsites@3.1.0: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chokidar@4.0.1: - dependencies: - readdirp: 4.0.2 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - commondir@1.0.1: {} - - concat-map@0.0.1: {} - - cookie@0.6.0: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - cssesc@3.0.0: {} - - debug@4.4.0: - dependencies: - ms: 2.1.3 - - deep-is@0.1.4: {} - - deepmerge@4.3.1: {} - - detect-libc@1.0.3: - optional: true - - devalue@5.1.1: {} - - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.1.0: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - entities@4.5.0: {} - - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - escape-string-regexp@4.0.0: {} - - eslint-compat-utils@0.5.1(eslint@9.17.0): - dependencies: - eslint: 9.17.0 - semver: 7.6.3 - - eslint-config-prettier@9.1.0(eslint@9.17.0): - dependencies: - eslint: 9.17.0 - - eslint-plugin-svelte@2.46.1(eslint@9.17.0)(svelte@5.13.0): - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@jridgewell/sourcemap-codec': 1.5.0 - eslint: 9.17.0 - eslint-compat-utils: 0.5.1(eslint@9.17.0) - esutils: 2.0.3 - known-css-properties: 0.35.0 - postcss: 8.4.49 - postcss-load-config: 3.1.4(postcss@8.4.49) - postcss-safe-parser: 6.0.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - semver: 7.6.3 - svelte-eslint-parser: 0.43.0(svelte@5.13.0) - optionalDependencies: - svelte: 5.13.0 - transitivePeerDependencies: - - ts-node - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-scope@8.2.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.0: {} - - eslint@9.17.0: - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.0 - escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - esm-env@1.2.1: {} - - espree@10.3.0: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 - - espree@9.6.1: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrap@1.2.3: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@2.0.2: {} - - esutils@2.0.3: {} - - fast-deep-equal@3.1.3: {} - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fdir@6.4.2(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.2 - keyv: 4.5.4 - - flatted@3.3.2: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - globals@14.0.0: {} - - globals@15.13.0: {} - - globalyzer@0.1.0: {} - - globrex@0.1.2: {} - - graphemer@1.4.0: {} - - has-flag@4.0.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - htmlparser2@8.0.2: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.1.0 - entities: 4.5.0 - - ignore@5.3.2: {} - - immutable@5.0.3: {} - - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-meta-resolve@4.1.0: {} - - imurmurhash@0.1.4: {} - - is-core-module@2.16.0: - dependencies: - hasown: 2.0.2 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-module@1.0.0: {} - - is-number@7.0.0: {} - - is-plain-object@5.0.0: {} - - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.6 - - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.6 - - isexe@2.0.0: {} - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kleur@4.1.5: {} - - known-css-properties@0.35.0: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lilconfig@2.1.0: {} - - linkify-it@5.0.0: - dependencies: - uc.micro: 2.1.0 - - locate-character@3.0.0: {} - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - - luxon@3.5.0: {} - - magic-string@0.30.15: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - - markdown-it@14.1.0: - dependencies: - argparse: 2.0.1 - entities: 4.5.0 - linkify-it: 5.0.0 - mdurl: 2.0.0 - punycode.js: 2.3.1 - uc.micro: 2.1.0 - - mdurl@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - minidenticons@4.2.1: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.1 - - mri@1.2.0: {} - - mrmime@2.0.0: {} - - ms@2.1.3: {} - - nanoid@3.3.8: {} - - natural-compare@1.4.0: {} - - node-addon-api@7.1.1: - optional: true - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-srcset@1.0.2: {} - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.2: {} - - postcss-load-config@3.1.4(postcss@8.4.49): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.4.49 - - postcss-safe-parser@6.0.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - postcss-scss@4.0.9(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss@8.4.49: - dependencies: - nanoid: 3.3.8 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.13.0): - dependencies: - prettier: 3.4.2 - svelte: 5.13.0 - - prettier@3.4.2: {} - - pretty-bytes@6.1.1: {} - - punycode.js@2.3.1: {} - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - readdirp@4.0.2: {} - - resolve-from@4.0.0: {} - - resolve@1.22.9: - dependencies: - is-core-module: 2.16.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.0.4: {} - - rollup@4.28.1: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.28.1 - '@rollup/rollup-android-arm64': 4.28.1 - '@rollup/rollup-darwin-arm64': 4.28.1 - '@rollup/rollup-darwin-x64': 4.28.1 - '@rollup/rollup-freebsd-arm64': 4.28.1 - '@rollup/rollup-freebsd-x64': 4.28.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 - '@rollup/rollup-linux-arm-musleabihf': 4.28.1 - '@rollup/rollup-linux-arm64-gnu': 4.28.1 - '@rollup/rollup-linux-arm64-musl': 4.28.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 - '@rollup/rollup-linux-riscv64-gnu': 4.28.1 - '@rollup/rollup-linux-s390x-gnu': 4.28.1 - '@rollup/rollup-linux-x64-gnu': 4.28.1 - '@rollup/rollup-linux-x64-musl': 4.28.1 - '@rollup/rollup-win32-arm64-msvc': 4.28.1 - '@rollup/rollup-win32-ia32-msvc': 4.28.1 - '@rollup/rollup-win32-x64-msvc': 4.28.1 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - sade@1.8.1: - dependencies: - mri: 1.2.0 - - sanitize-html@2.13.1: - dependencies: - deepmerge: 4.3.1 - escape-string-regexp: 4.0.0 - htmlparser2: 8.0.2 - is-plain-object: 5.0.0 - parse-srcset: 1.0.2 - postcss: 8.4.49 - - sass@1.83.0: - dependencies: - chokidar: 4.0.1 - immutable: 5.0.3 - source-map-js: 1.2.1 - optionalDependencies: - '@parcel/watcher': 2.5.0 - - semver@7.6.3: {} - - set-cookie-parser@2.7.1: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - sirv@3.0.0: - dependencies: - '@polka/url': 1.0.0-next.28 - mrmime: 2.0.0 - totalist: 3.0.1 - - source-map-js@1.2.1: {} - - strip-json-comments@3.1.1: {} - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - svelte-bootstrap-icons@3.1.1: {} - - svelte-check@4.1.1(picomatch@4.0.2)(svelte@5.13.0)(typescript@5.7.2): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - chokidar: 4.0.1 - fdir: 6.4.2(picomatch@4.0.2) - picocolors: 1.1.1 - sade: 1.8.1 - svelte: 5.13.0 - typescript: 5.7.2 - transitivePeerDependencies: - - picomatch - - svelte-eslint-parser@0.43.0(svelte@5.13.0): - dependencies: - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - postcss: 8.4.49 - postcss-scss: 4.0.9(postcss@8.4.49) - optionalDependencies: - svelte: 5.13.0 - - svelte-tippy@1.3.2: - dependencies: - tippy.js: 6.3.7 - - svelte@5.13.0: - dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.14.0 - acorn-typescript: 1.4.13(acorn@8.14.0) - aria-query: 5.3.2 - axobject-query: 4.1.0 - esm-env: 1.2.1 - esrap: 1.2.3 - is-reference: 3.0.3 - locate-character: 3.0.0 - magic-string: 0.30.15 - zimmerframe: 1.1.2 - - sveltekit-i18n@2.4.2(svelte@5.13.0): - dependencies: - '@sveltekit-i18n/base': 1.3.7(svelte@5.13.0) - '@sveltekit-i18n/parser-default': 1.1.1 - svelte: 5.13.0 - - tiny-glob@0.2.9: - dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 - - tippy.js@6.3.7: - dependencies: - '@popperjs/core': 2.11.8 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - totalist@3.0.1: {} - - ts-api-utils@1.4.3(typescript@5.7.2): - dependencies: - typescript: 5.7.2 - - tslog@4.9.3: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.18.0(eslint@9.17.0)(typescript@5.7.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.18.0(@typescript-eslint/parser@8.18.0(eslint@9.17.0)(typescript@5.7.2))(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/parser': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.0(eslint@9.17.0)(typescript@5.7.2) - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - typescript@5.7.2: {} - - uc.micro@2.1.0: {} - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - util-deprecate@1.0.2: {} - - vite@5.4.11(sass@1.83.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.28.1 - optionalDependencies: - fsevents: 2.3.3 - sass: 1.83.0 - - vitefu@1.0.4(vite@5.4.11(sass@1.83.0)): - optionalDependencies: - vite: 5.4.11(sass@1.83.0) - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - - yaml@1.10.2: {} - - yocto-queue@0.1.0: {} - - zimmerframe@1.1.2: {} diff --git a/Foxnouns.Frontend/src/app.d.ts b/Foxnouns.Frontend/src/app.d.ts index da08e6d..243d72d 100644 --- a/Foxnouns.Frontend/src/app.d.ts +++ b/Foxnouns.Frontend/src/app.d.ts @@ -1,7 +1,17 @@ // See https://svelte.dev/docs/kit/types#app.d.ts + +import type { ErrorCode } from "$api/error"; + // for information about these interfaces declare global { namespace App { + interface Error { + message: string; + status: number; + code: ErrorCode; + errors?: Array<{ key: string; errors: ValidationError[] }>; + error_id?: string; + } // interface Error {} // interface Locals {} // interface PageData {} diff --git a/Foxnouns.Frontend/src/app.scss b/Foxnouns.Frontend/src/app.scss index e1d59ab..c678355 100644 --- a/Foxnouns.Frontend/src/app.scss +++ b/Foxnouns.Frontend/src/app.scss @@ -64,3 +64,11 @@ max-width: 200px; border-radius: 3px; } + +.big-footer { + @media (prefers-color-scheme: dark) { + background-color: bootstrap.shade-color(bootstrap.$dark, 20%); + } + + background-color: bootstrap.shade-color(bootstrap.$light, 5%); +} diff --git a/Foxnouns.Frontend/src/hooks.server.ts b/Foxnouns.Frontend/src/hooks.server.ts index e8ec723..35a0048 100644 --- a/Foxnouns.Frontend/src/hooks.server.ts +++ b/Foxnouns.Frontend/src/hooks.server.ts @@ -1,6 +1,10 @@ +import ApiError, { ErrorCode } from "$api/error"; import { PRIVATE_API_HOST, PRIVATE_INTERNAL_API_HOST } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { PUBLIC_API_BASE } from "$env/static/public"; -import type { HandleFetch } from "@sveltejs/kit"; +import log from "$lib/log"; +import type { HandleFetch, HandleServerError } from "@sveltejs/kit"; +import * as Sentry from "@sentry/sveltekit"; export const handleFetch: HandleFetch = async ({ request, fetch }) => { if (request.url.startsWith(`${PUBLIC_API_BASE}/internal`)) { @@ -11,3 +15,33 @@ export const handleFetch: HandleFetch = async ({ request, fetch }) => { return await fetch(request); }; + +Sentry.init({ + dsn: env.PRIVATE_SENTRY_DSN, +}); + +export const handleError: HandleServerError = async ({ error, status, message }) => { + if (error instanceof ApiError) { + return { + status: error.raw?.status || status, + message: error.raw?.message || "Unknown error", + code: error.code, + }; + } + + if (status >= 400 && status <= 499) { + return { status, message, code: ErrorCode.GenericApiError }; + } + + // client errors and backend API errors just clog up sentry, so we don't send those. + const id = Sentry.captureException(error, { + mechanism: { + type: "sveltekit", + handled: false, + }, + }); + + log.error("[%s] error in handler:", id, error); + + return { error_id: id, status, message, code: ErrorCode.InternalServerError }; +}; diff --git a/Foxnouns.Frontend/src/lib/actions/callback.ts b/Foxnouns.Frontend/src/lib/actions/callback.ts index 3df1070..5c525ab 100644 --- a/Foxnouns.Frontend/src/lib/actions/callback.ts +++ b/Foxnouns.Frontend/src/lib/actions/callback.ts @@ -4,10 +4,12 @@ import type { AddAccountResponse, CallbackResponse } from "$api/models"; import { setToken } from "$lib"; import log from "$lib/log"; import { isRedirect, redirect, type ServerLoadEvent } from "@sveltejs/kit"; +import type { TicketData } from "../../routes/auth/callback/register/[ticket]/+page.server"; export default function createCallbackLoader( callbackType: string, bodyFn?: (event: ServerLoadEvent) => Promise, + returnData?: boolean, ) { return async (event: ServerLoadEvent) => { const { parent, fetch, cookies } = event; @@ -53,12 +55,23 @@ export default function createCallbackLoader( redirect(303, `/@${resp.user!.username}`); } - return { - hasAccount: false, - isLinkRequest: false, - ticket: resp.ticket!, - remoteUser: resp.remote_username!, - }; + if (returnData) + return { + ticket: resp.ticket!, + remoteUser: resp.remote_username!, + }; + + const ticket = btoa( + JSON.stringify({ + type: callbackType, + ticket: resp.ticket!, + remoteUsername: resp.remote_username!, + } satisfies TicketData), + ) + .replaceAll("+", "-") + .replaceAll("/", "_"); + + redirect(303, "/auth/callback/register/" + ticket); } catch (e) { if (isRedirect(e)) throw e; if (e instanceof ApiError) return { isLinkRequest: false, error: e.obj }; diff --git a/Foxnouns.Frontend/src/lib/actions/modaction.ts b/Foxnouns.Frontend/src/lib/actions/modaction.ts new file mode 100644 index 0000000..15fd16f --- /dev/null +++ b/Foxnouns.Frontend/src/lib/actions/modaction.ts @@ -0,0 +1,90 @@ +import { apiRequest } from "$api"; +import ApiError, { ErrorCode, type RawApiError } from "$api/error"; +import type { AuditLogEntry, ClearableField } from "$api/models/moderation"; +import log from "$lib/log"; +import { type RequestEvent } from "@sveltejs/kit"; + +type ModactionResponse = { ok: boolean; resp: AuditLogEntry | null; error: RawApiError | null }; +type ModactionFunction = (evt: RequestEvent) => Promise; + +export default function createModactionAction( + type: "ignore" | "warn" | "suspend", + requireReason: boolean, +): ModactionFunction { + return async function ({ request, fetch, cookies }) { + const body = await request.formData(); + const userId = body.get("user") as string; + const memberId = body.get("member") as string | null; + const reportId = body.get("report") as string | null; + const reason = body.get("reason") as string | null; + + if (!reportId && type === "ignore") { + return { + ok: false, + resp: null, + error: { + status: 400, + message: "Bad request", + code: ErrorCode.BadRequest, + errors: [ + { key: "report", errors: [{ message: "Ignoring a report requires a report ID" }] }, + ], + } satisfies RawApiError, + }; + } + + if (!reason && requireReason) { + return { + ok: false, + resp: null, + error: { + status: 400, + message: "Bad request", + code: ErrorCode.BadRequest, + errors: [{ key: "reason", errors: [{ message: "You must give a reason" }] }], + } satisfies RawApiError, + }; + } + + let clearFields: ClearableField[] | undefined = undefined; + if (type === "warn") { + clearFields = body.getAll("clear-fields") as ClearableField[]; + } + + let path: string; + if (type === "warn") path = `/moderation/warnings/${userId}`; + else if (type === "suspend") path = `/moderation/suspensions/${userId}`; + else path = `/moderation/reports/${reportId}/ignore`; + + try { + const resp = await apiRequest("POST", path, { + fetch, + cookies, + body: { + reason: reason, + // These are ignored by POST /reports/{id}/ignore + member_id: memberId, + report_id: reportId, + // This is ignored by everything but POST /warnings/{id} + clear_fields: clearFields, + // This is ignored by everything but POST /suspensions/{id} + clear_profile: !!body.get("clear-profile"), + }, + }); + + return { ok: true, resp, error: null }; + } catch (e) { + if (e instanceof ApiError) return { ok: false, error: e.obj, resp: null }; + log.error("could not take action on %s:", path, e); + throw e; + } + }; +} + +export function createModactions() { + return { + ignore: createModactionAction("ignore", false), + warn: createModactionAction("warn", true), + suspend: createModactionAction("suspend", true), + }; +} diff --git a/Foxnouns.Frontend/src/lib/api/error.ts b/Foxnouns.Frontend/src/lib/api/error.ts index 1fd2041..e893a86 100644 --- a/Foxnouns.Frontend/src/lib/api/error.ts +++ b/Foxnouns.Frontend/src/lib/api/error.ts @@ -14,6 +14,7 @@ export default class ApiError { toObject(): RawApiError { return { + error_id: this.raw?.error_id, status: this.raw?.status || 500, code: this.code, message: this.raw?.message || "Internal server error", @@ -23,6 +24,7 @@ export default class ApiError { } export type RawApiError = { + error_id?: string; status: number; message: string; code: ErrorCode; @@ -41,6 +43,7 @@ export enum ErrorCode { MemberNotFound = "MEMBER_NOT_FOUND", AccountAlreadyLinked = "ACCOUNT_ALREADY_LINKED", LastAuthMethod = "LAST_AUTH_METHOD", + PageNotFound = "PAGE_NOT_FOUND", // This code isn't actually returned by the API Non204Response = "(non 204 response)", } diff --git a/Foxnouns.Frontend/src/lib/api/index.ts b/Foxnouns.Frontend/src/lib/api/index.ts index 0a4047d..e52918a 100644 --- a/Foxnouns.Frontend/src/lib/api/index.ts +++ b/Foxnouns.Frontend/src/lib/api/index.ts @@ -9,7 +9,7 @@ export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; /** * Optional arguments for a request. `load` and `action` functions should always pass `fetch` and `cookies`. */ -export type RequestArgs = { +export type RequestArgs = { /** * The token for this request. Where possible, `cookies` should be passed instead. * Will override `cookies` if both are passed. @@ -23,7 +23,7 @@ export type RequestArgs = { /** * The body for this request, which will be serialized to JSON. Should be a plain JS object. */ - body?: unknown; + body?: T; /** * The fetch function to use. Should be passed in loader and action functions, but can be safely ignored for client-side requests. */ @@ -41,10 +41,10 @@ export type RequestArgs = { * @param args Optional arguments to the request function. * @returns A Response object. */ -export async function baseRequest( +export async function baseRequest( method: Method, path: string, - args: RequestArgs = {}, + args: RequestArgs = {}, ): Promise { const token = args.token ?? args.cookies?.get(TOKEN_COOKIE_NAME); @@ -72,19 +72,20 @@ export async function baseRequest( * @param args Optional arguments to the request function. * @returns The response deserialized as `T`. */ -export async function apiRequest( +export async function apiRequest( method: Method, path: string, - args: RequestArgs = {}, -): Promise { + args: RequestArgs = {}, +): Promise { const resp = await baseRequest(method, path, args); if (resp.status < 200 || resp.status > 299) { const err = await resp.json(); + log.error("Received error for request to %s %s:", method, path, err); if ("code" in err) throw new ApiError(err); else throw new ApiError(); } - return (await resp.json()) as T; + return (await resp.json()) as TResponse; } /** @@ -94,10 +95,10 @@ export async function apiRequest( * @param args Optional arguments to the request function. * @param enforce204 Whether to throw an error on a non-204 status code. */ -export async function fastRequest( +export async function fastRequest( method: Method, path: string, - args: RequestArgs = {}, + args: RequestArgs = {}, enforce204: boolean = false, ): Promise { const resp = await baseRequest(method, path, args); diff --git a/Foxnouns.Frontend/src/lib/api/models/meta.ts b/Foxnouns.Frontend/src/lib/api/models/meta.ts index 56f31c9..28ea494 100644 --- a/Foxnouns.Frontend/src/lib/api/models/meta.ts +++ b/Foxnouns.Frontend/src/lib/api/models/meta.ts @@ -10,6 +10,7 @@ export type Meta = { }; members: number; limits: Limits; + notice: { id: string; message: string } | null; }; export type Limits = { diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts new file mode 100644 index 0000000..689e9b8 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/api/models/moderation.ts @@ -0,0 +1,123 @@ +import type { Member } from "./member"; +import type { AuthMethod, PartialMember, PartialUser, User } from "./user"; + +export type CreateReportRequest = { + reason: ReportReason; + context: string | null; +}; + +export enum ReportReason { + Totalitarianism = "TOTALITARIANISM", + HateSpeech = "HATE_SPEECH", + Racism = "RACISM", + Homophobia = "HOMOPHOBIA", + Transphobia = "TRANSPHOBIA", + Queerphobia = "QUEERPHOBIA", + Exclusionism = "EXCLUSIONISM", + Sexism = "SEXISM", + Ableism = "ABLEISM", + ChildPornography = "CHILD_PORNOGRAPHY", + PedophiliaAdvocacy = "PEDOPHILIA_ADVOCACY", + Harassment = "HARASSMENT", + Impersonation = "IMPERSONATION", + Doxxing = "DOXXING", + EncouragingSelfHarm = "ENCOURAGING_SELF_HARM", + Spam = "SPAM", + Trolling = "TROLLING", + Advertisement = "ADVERTISEMENT", + CopyrightViolation = "COPYRIGHT_VIOLATION", +} + +export type Report = { + id: string; + reporter: PartialUser; + target_user: PartialUser; + target_member?: PartialMember; + status: "OPEN" | "CLOSED"; + reason: ReportReason; + context: string | null; + target_type: "USER" | "MEMBER"; + snapshot: User | Member | null; +}; + +export type AuditLogEntry = { + id: string; + moderator: AuditLogEntity; + target_user?: AuditLogEntity; + target_member?: AuditLogEntity; + report?: PartialReport; + type: AuditLogEntryType; + reason: string | null; + cleared_fields?: string[]; +}; + +export type AuditLogEntity = { id: string; username: string }; + +export enum AuditLogEntryType { + IgnoreReport = "IGNORE_REPORT", + WarnUser = "WARN_USER", + WarnUserAndClearProfile = "WARN_USER_AND_CLEAR_PROFILE", + SuspendUser = "SUSPEND_USER", + QuerySensitiveUserData = "QUERY_SENSITIVE_USER_DATA", +} + +export type PartialReport = { + id: string; + reporter_id: string; + target_user_id: string; + target_member_id?: string; + reason: ReportReason; + context: string | null; + target_type: "USER" | "MEMBER"; +}; + +export type ReportDetails = { + report: Report; + user: User; + member?: Member; + audit_log_entry?: AuditLogEntry; +}; + +export type QueriedUser = { + user: User; + member_list_hidden: boolean; + last_active: string; + last_sid_reroll: string; + suspended: boolean; + deleted: boolean; + auth_methods?: AuthMethod[]; +}; + +export type WarnUserRequest = { + reason: string; + clear_fields?: ClearableField[]; + member_id?: string; + report_id?: string; +}; + +export type SuspendUserRequest = { + reason: string; + clear_profile: boolean; + report_id?: string; +}; + +export enum ClearableField { + DisplayName = "DISPLAY_NAME", + Avatar = "AVATAR", + Bio = "BIO", + Links = "LINKS", + Names = "NAMES", + Pronouns = "PRONOUNS", + Fields = "FIELDS", + Flags = "FLAGS", + CustomPreferences = "CUSTOM_PREFERENCES", +} + +export type Notification = { + id: string; + type: "NOTICE" | "WARNING" | "SUSPENSION"; + message?: string; + localization_key?: string; + localization_params: Record; + acknowledged: boolean; +}; diff --git a/Foxnouns.Frontend/src/lib/api/models/user.ts b/Foxnouns.Frontend/src/lib/api/models/user.ts index 6cd8e4c..be9d961 100644 --- a/Foxnouns.Frontend/src/lib/api/models/user.ts +++ b/Foxnouns.Frontend/src/lib/api/models/user.ts @@ -28,6 +28,7 @@ export type MeUser = UserWithMembers & { timezone: string; suspended: boolean; deleted: boolean; + settings: UserSettings; }; export type UserWithMembers = User & { members: PartialMember[] | null }; @@ -40,6 +41,7 @@ export type UserWithHiddenFields = User & { export type UserSettings = { dark_mode: boolean | null; + last_read_notice: string | null; }; export type PartialMember = { diff --git a/Foxnouns.Frontend/src/lib/components/ClientPaginator.svelte b/Foxnouns.Frontend/src/lib/components/ClientPaginator.svelte index 7f72ca2..7b08026 100644 --- a/Foxnouns.Frontend/src/lib/components/ClientPaginator.svelte +++ b/Foxnouns.Frontend/src/lib/components/ClientPaginator.svelte @@ -5,8 +5,11 @@ currentPage: number; pageCount: number; center?: boolean; + listAllPages?: boolean; }; - let { currentPage = $bindable(), pageCount, center }: Props = $props(); + let { currentPage = $bindable(), pageCount, center, listAllPages }: Props = $props(); + + let allPages = $derived(listAllPages === undefined || listAllPages); let prevPage = $derived(currentPage > 0 ? currentPage - 1 : 0); let nextPage = $derived(currentPage < pageCount - 1 ? currentPage + 1 : pageCount - 1); @@ -21,11 +24,27 @@ (currentPage = prevPage)} /> - {#each new Array(pageCount) as _, page} - - (currentPage = page)}>{page + 1} + {#if allPages} + {#each new Array(pageCount) as _, page} + + (currentPage = page)}>{page + 1} + + {/each} + {:else} + {#if currentPage !== 0} + (currentPage = prevPage)}> + {currentPage} + + {/if} + + {currentPage + 1} - {/each} + {#if currentPage !== pageCount - 1} + (currentPage = nextPage)}> + {currentPage + 2} + + {/if} + {/if} (currentPage = nextPage)} /> diff --git a/Foxnouns.Frontend/src/lib/components/Error.svelte b/Foxnouns.Frontend/src/lib/components/Error.svelte index 64f3bc7..c9e2c0e 100644 --- a/Foxnouns.Frontend/src/lib/components/Error.svelte +++ b/Foxnouns.Frontend/src/lib/components/Error.svelte @@ -12,12 +12,22 @@ {#if error.code === ErrorCode.BadRequest} {$t("error.bad-request-header")} + {:else if error.status === 404} + {$t("error.not-found-header")} {:else} {$t("error.generic-header")} {/if} {/if}

{errorDescription($t, error.code)}

+{#if error.error_id} +

+ {$t("error.error-id")} + + {error.error_id} + +

+{/if} {#if error.errors}
{$t("error.extra-info-header")} diff --git a/Foxnouns.Frontend/src/lib/components/Footer.svelte b/Foxnouns.Frontend/src/lib/components/Footer.svelte new file mode 100644 index 0000000..857c07c --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/Footer.svelte @@ -0,0 +1,84 @@ + + + diff --git a/Foxnouns.Frontend/src/lib/components/GlobalNotice.svelte b/Foxnouns.Frontend/src/lib/components/GlobalNotice.svelte new file mode 100644 index 0000000..c8a55f1 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/GlobalNotice.svelte @@ -0,0 +1,50 @@ + + +{#if renderNotice} + +{/if} diff --git a/Foxnouns.Frontend/src/lib/components/Navbar.svelte b/Foxnouns.Frontend/src/lib/components/Navbar.svelte index 68c1b14..2074347 100644 --- a/Foxnouns.Frontend/src/lib/components/Navbar.svelte +++ b/Foxnouns.Frontend/src/lib/components/Navbar.svelte @@ -8,29 +8,39 @@ NavLink, NavItem, } from "@sveltestrap/sveltestrap"; - import { page } from "$app/stores"; + import { page } from "$app/state"; import type { Meta, MeUser } from "$api/models/index"; import Logo from "$components/Logo.svelte"; import { t } from "$lib/i18n"; - type Props = { user: MeUser | null; meta: Meta }; - let { user, meta }: Props = $props(); + type Props = { user: MeUser | null; meta: Meta; unreadNotifications?: boolean }; + let { user, meta, unreadNotifications }: Props = $props(); let isOpen = $state(true); const toggleMenu = () => (isOpen = !isOpen); +{#if user && unreadNotifications} +
+ {$t("nav.unread-notification-text")} +
+ {$t("nav.unread-notification-link")} +
+{/if} + {#if user && user.deleted} {/if} @@ -51,19 +61,26 @@ @{user.username} + {#if user.role === "ADMIN" || user.role === "MODERATOR"} + + + Administration + + + {/if} - + {$t("nav.settings")} {:else} - + {$t("nav.log-in")} @@ -78,6 +95,11 @@ background-color: var(--bs-danger-bg-subtle); } + .notification-alert { + color: var(--bs-warning-text-emphasis); + background-color: var(--bs-warning-bg-subtle); + } + /* These exact values make it look almost identical to the SVG version, which is what we want */ #beta-text { font-size: 0.7em; diff --git a/Foxnouns.Frontend/src/lib/components/RequiredFieldMarker.svelte b/Foxnouns.Frontend/src/lib/components/RequiredFieldMarker.svelte new file mode 100644 index 0000000..f67d9ef --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/RequiredFieldMarker.svelte @@ -0,0 +1,12 @@ + + +{#if required} + * +{:else} + {$t("form.optional")} +{/if} diff --git a/Foxnouns.Frontend/src/lib/components/URLAlert.svelte b/Foxnouns.Frontend/src/lib/components/URLAlert.svelte new file mode 100644 index 0000000..72689ab --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/URLAlert.svelte @@ -0,0 +1,14 @@ + + +{#if key} +
+ {$t(key)} +
+{/if} diff --git a/Foxnouns.Frontend/src/lib/components/admin/ActionForm.svelte b/Foxnouns.Frontend/src/lib/components/admin/ActionForm.svelte new file mode 100644 index 0000000..3c7f741 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/ActionForm.svelte @@ -0,0 +1,73 @@ + + +
+ + {#if memberId} + + {/if} + {#if reportId} + + {/if} + + + + {#if reportId} + + + + {/if} + +
+ {#each fields as field} +
+ + +
+ {/each} +
+
+ +
+
+ +
+ + +
+ +
+
+ diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte new file mode 100644 index 0000000..1f3645f --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntity.svelte @@ -0,0 +1,8 @@ + + +{entity.username} ({entity.id}) diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte new file mode 100644 index 0000000..b1d777a --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte @@ -0,0 +1,68 @@ + + +
+
+ + + {#if entry.type === "IGNORE_REPORT"} + ignored a report + {:else if entry.type === "WARN_USER" || entry.type === "WARN_USER_AND_CLEAR_PROFILE"} + warned + {:else if entry.type === "SUSPEND_USER"} + suspended + {:else if entry.type === "QUERY_SENSITIVE_USER_DATA"} + looked up sensitive data of + {:else} + (unknown action {entry.type}) + {/if} + {#if entry.target_user} + + {/if} + {#if entry.target_member} + for member + {/if} + + + {date} +
+ + {#if entry.type === "IGNORE_REPORT"} + {#if entry.report} +
+ Report +
    +
  • From: {entry.report.reporter_id}
  • +
  • Target: {entry.report.target_user_id}
  • +
  • Reason: {entry.report.reason}
  • + {#if entry.report.context} +
  • Context: {entry.report.context}
  • + {/if} +
+
+ {:else} +

(the ignored report has been deleted)

+ {/if} + {/if} + + {#if reason} +
+ Reason + + {@html reason} +
+ {:else} +

(no reason given)

+ {/if} +
diff --git a/Foxnouns.Frontend/src/lib/components/admin/ClosedReportAuditLog.svelte b/Foxnouns.Frontend/src/lib/components/admin/ClosedReportAuditLog.svelte new file mode 100644 index 0000000..589539f --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/ClosedReportAuditLog.svelte @@ -0,0 +1,38 @@ + + +
+

+ Closed by at + {idTimestamp(entry.id).toLocaleString(DateTime.DATETIME_MED)} +

+

+ {#if entry.type === AuditLogEntryType.IgnoreReport} + Report was ignored + {:else if entry.type === AuditLogEntryType.WarnUser || entry.type === AuditLogEntryType.WarnUserAndClearProfile} + User was warned + {#if entry.cleared_fields && entry.cleared_fields.length > 0} +
Cleared fields: {entry.cleared_fields.join(", ")} + {/if} + {:else if entry.type === AuditLogEntryType.SuspendUser} + User was suspended + {/if} +

+

Reason

+

+ {#if entry.reason} + + {@html renderMarkdown(entry.reason)} + {:else} + (no reason given) + {/if} +

+
diff --git a/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte new file mode 100644 index 0000000..a6d04e4 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/DashboardCard.svelte @@ -0,0 +1,17 @@ + + +
+
+
+
{title}
+

+ {@render children()} +

+
+
+
diff --git a/Foxnouns.Frontend/src/lib/components/admin/PartialProfileCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/PartialProfileCard.svelte new file mode 100644 index 0000000..885ca82 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/admin/PartialProfileCard.svelte @@ -0,0 +1,29 @@ + + +
+ + + +

+ + @{user.username} + +

+

Created {createdAt}

+
diff --git a/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte index e18c6b6..288bcc5 100644 --- a/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte +++ b/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte @@ -1,7 +1,8 @@ @@ -44,6 +86,41 @@

+ (cropperOpen = !cropperOpen)} +> + + + + + + + {#if meUser && meUser.username !== user} + {$t("profile.report-button")} + {/if} + diff --git a/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte b/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte index 692146a..f1c7964 100644 --- a/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte +++ b/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte @@ -2,8 +2,8 @@ import { t } from "$lib/i18n"; import type { AuthMethod } from "$api/models"; - type Props = { method: AuthMethod; canRemove: boolean }; - let { method, canRemove }: Props = $props(); + type Props = { method: AuthMethod; canRemove: boolean; showType?: boolean }; + let { method, canRemove, showType }: Props = $props(); let name = $derived( method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id), @@ -14,6 +14,9 @@
+ {#if showType} + {method.type}: + {/if} {name} {#if showId}({method.remote_id}){/if}
diff --git a/Foxnouns.Frontend/src/lib/components/settings/Notification.svelte b/Foxnouns.Frontend/src/lib/components/settings/Notification.svelte new file mode 100644 index 0000000..796d60a --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/settings/Notification.svelte @@ -0,0 +1,43 @@ + + +
+
+
+ +
+

+ {#if notification.localization_key} + {$t(notification.localization_key, notification.localization_params)} + {:else} + {notification.message} + {/if} +

+
+
+
+ +
diff --git a/Foxnouns.Frontend/src/lib/errorCodes.ts b/Foxnouns.Frontend/src/lib/errorCodes.ts index b97b71b..8b8ef44 100644 --- a/Foxnouns.Frontend/src/lib/errorCodes.ts +++ b/Foxnouns.Frontend/src/lib/errorCodes.ts @@ -29,6 +29,8 @@ export default function errorDescription(t: TranslateFn, code: ErrorCode): strin return t("error.account-already-linked"); case ErrorCode.LastAuthMethod: return t("error.last-auth-method"); + case ErrorCode.PageNotFound: + return t("error.page-not-found"); case ErrorCode.Non204Response: return t("error.generic-error"); } diff --git a/Foxnouns.Frontend/src/lib/i18n/locales/en.json b/Foxnouns.Frontend/src/lib/i18n/locales/en.json index 2ede9a1..22428f7 100644 --- a/Foxnouns.Frontend/src/lib/i18n/locales/en.json +++ b/Foxnouns.Frontend/src/lib/i18n/locales/en.json @@ -5,9 +5,13 @@ "settings": "Settings", "suspended-account-hint": "Your account has been suspended. Your profile has been hidden and you will not be able to change any settings.", "appeal-suspension-link": "I want to appeal", - "deleted-account-hint": "You have requested deletion of your account. If you want to reactivate it, click the link below.", + "deleted-account-hint": "You have requested deletion of your account.", "reactivate-account-link": "Reactivate account", - "delete-permanently-link": "I want my account deleted permanently" + "delete-permanently-link": "I want my account deleted permanently", + "reactivate-or-delete-link": "I want to reactivate my account or delete all my data", + "export-link": "I want to export a copy of my data", + "unread-notification-text": "You have an unread notification.", + "unread-notification-link": "Go to your notifications" }, "avatar-tooltip": "Avatar for {{name}}", "profile": { @@ -18,7 +22,10 @@ "pronouns-header": "Pronouns", "default-members-header": "Members", "create-member-button": "Create member", - "back-to-user": "Back to {{name}}" + "back-to-user": "Back to {{name}}", + "copy-link-button": "Copy link", + "copy-short-link-button": "Copy short link", + "report-button": "Report profile" }, "title": { "log-in": "Log in", @@ -41,7 +48,7 @@ "remote-fediverse-account-label": "Your Fediverse account", "register-username-label": "Username", "register-button": "Register account", - "register-with-mastodon": "Register with a Fediverse account", + "register-with-fediverse": "Register with a Fediverse account", "log-in-with-fediverse-error-blurb": "Is your instance returning an error?", "log-in-with-fediverse-force-refresh-button": "Force a refresh on our end", "register-with-discord": "Register with a Discord account", @@ -72,7 +79,17 @@ "log-in-sign-up-link": "Sign up with email", "forgot-password-title": "Forgot password", "reset-password-title": "Reset password", - "password-changed-hint": "Your password has been changed!" + "password-changed-hint": "Your password has been changed!", + "link-email-header": "Link a new email address", + "unlink-email-header": "Unlink email address", + "unlink-fediverse-header": "Unlink fediverse account", + "unlink-tumblr-header": "Unlink Tumblr account", + "unlink-google-header": "Unlink Google account", + "unlink-discord-header": "Unlink Discord account", + "unlink-confirmation-1": "Are you sure you want to unlink {{username}} from your account?", + "unlink-confirmation-2": "You will no longer be able to use this account to log in. Please make sure at least one of your other linked accounts is accessible before continuing.", + "unlink-button": "Unlink account", + "log-in-3rd-party-desc-no-email": "You can use any of the following services to log in. You can add or remove others at any time." }, "error": { "bad-request-header": "Something was wrong with your input", @@ -105,7 +122,10 @@ "back-to-prev-page-button": "Go back to the previous page", "400-description": "Something went wrong with your request. This error should never land you on this page, so it's probably a bug.", "500-description": "Something went wrong on the server. Please try again later.", - "unknown-status-description": "Something went wrong, but we're not sure what. Please try again." + "unknown-status-description": "Something went wrong, but we're not sure what. Please try again.", + "error-id": "If you report this error to the developers, please give them this ID:", + "page-not-found": "No page exists at this URL.", + "not-found-header": "That page could not be found" }, "settings": { "general-information-tab": "General information", @@ -152,7 +172,44 @@ "flag-description-placeholder": "Description", "flag-name-placeholder": "Name", "flag-upload-success": "Successfully uploaded your flag! It may take a few seconds before it's saved.", - "custom-preferences-title": "Custom preferences" + "custom-preferences-title": "Custom preferences", + "change-username-header": "Change your username", + "force-delete-button": "Delete my account permanently", + "force-delete-warning": "This is irreversible. Consider exporting a copy of your data before doing this.", + "force-delete-explanation": "Your account is currently pending deletion. If you want your data deleted permanently, use the button below.", + "reactivate-explanation": "Your account is currently pending deletion. If you want to cancel this and keep using your account, use the link below.", + "reactivate-header": "Reactivate your account", + "force-delete-header": "Permanently delete your account", + "reactivate-button": "Reactivate my account", + "reactivated-header": "Account reactivated", + "reactivated-explanation": "Your account has been reactivated!", + "force-delete-input-label": "To delete your account, type your username (@{{username}}), including the @, in the box below:", + "force-delete-export-hint": "If you haven't done so yet, we recommend you download an export of your data before continuing:", + "force-delete-export-link": "export your data", + "force-delete-irreversible": "This process is irreversible.", + "force-delete-username-available": "Your username will immediately be available for other users to take.", + "force-delete-immediate-delete": "This will immediately delete all of your profiles, including avatars.", + "force-delete-page-explanation": "Your account is currently pending deletion. If you want all your data deleted immediately, you can do so here.", + "force-delete-page-header": "Permanently delete your account", + "force-delete-checkbox-label": "Yes, I understand that my data will be permanently deleted and cannot be recovered.", + "force-delete-page-button": "Delete my account", + "account-is-deleted-header": "Your account has been deleted", + "account-is-deleted-permanently-description": "Your account has been deleted. Note that it may take a few minutes for all of your data to be removed.", + "account-is-deleted-close-page": "You may now close this page.", + "soft-delete-button": "Deactivate your account", + "soft-delete-hint": "If you want to delete your account, use the button below.", + "soft-delete-header": "Deactivate your account", + "force-delete-page-cancel": "I changed my mind, cancel", + "soft-delete-page-header": "Deactivate your account", + "soft-delete-page-explanation": "If you want to delete your account, you can do so here.", + "soft-delete-90-days": "Your account will be permanently deleted after 90 days.", + "soft-delete-can-reactivate": "If you change your mind, you can log in and go to the settings page at any time to reactivate your account.", + "soft-delete-keep-username": "You will keep your current username until your account is permanently deleted.", + "soft-delete-can-delete-permanently": "If you want to delete all your data early, you can do so by logging in and going to the settings page.", + "soft-delete-page-button": "Deactivate my account", + "soft-delete-input-label": "To deactivate your account, type your username (@{{username}}), including the @, in the box below:", + "account-is-deactivated-header": "Your account has been deactivated", + "account-is-deactivated-description": "Your account has been deactivated, and will be deleted in 90 days. If you change your mind, just log in again, and you will have the option to reactivate your account. If you want to delete your data immediately, you should also log in again, and you will be able to request immediate deletion." }, "yes": "Yes", "no": "No", @@ -235,7 +292,67 @@ "custom-preference-size-small": "Small", "custom-preference-size": "Text size", "custom-preference-muted": "Show as muted text", - "custom-preference-favourite": "Treat like favourite" + "custom-preference-favourite": "Treat like favourite", + "custom-preference-notice": "Want to edit your custom preferences?", + "custom-preference-notice-link": "Go to settings", + "crop-avatar-header": "Crop avatar", + "crop-avatar-button": "Crop", + "max-custom-preferences": "You have reached the maximum amount of custom preferences ({{current}}/{{max}}), and cannot add new ones." }, - "cancel": "Cancel" + "cancel": "Cancel", + "report": { + "title": "Reporting {{name}}", + "totalitarianism": "Support of totalitarian regimes", + "hate-speech": "Hate speech", + "racism": "Racism or xenophobia", + "homophobia": "Homophobia", + "transphobia": "Transphobia", + "queerphobia": "Queerphobia (other)", + "exclusionism": "Queer or plural exclusionism", + "sexism": "Sexism or misogyny", + "ableism": "Ableism", + "child-pornography": "Child pornography", + "pedophilia-advocacy": "Pedophilia advocacy", + "harassment": "Harassment", + "impersonation": "Impersonation", + "doxxing": "Doxxing", + "encouraging-self-harm": "Encouraging self-harm or suicide", + "spam": "Spam", + "trolling": "Trolling", + "advertisement": "Advertising", + "copyright-violation": "Copyright or trademark violation", + "success": "Successfully submitted report!", + "reason-label": "Why are you reporting this profile?", + "context-label": "Is there any context you'd like to give us?", + "submit-button": "Submit report" + }, + "form": { + "optional": "(optional)", + "required": "Required" + }, + "alert": { + "auth-method-remove-success": "Successfully unlinked account!", + "auth-required": "You must log in to access this page.", + "notif-ack-successful": "Successfully marked notification as read!", + "notif-ack-fail": "Could not mark notification as read." + }, + "footer": { + "version": "Version", + "users": "users", + "members": "members", + "source": "Source code", + "status": "Status", + "terms": "Terms of service", + "privacy": "Privacy policy", + "changelog": "Changelog", + "donate": "Donate", + "about-contact": "About and contact" + }, + "notification": { + "suspension": "Your account has been suspended for the following reason: {{reason}}", + "warning": "You have been warned for the following reason: {{reason}}", + "warning-cleared-fields": "You have been warned for the following reason: {{reason}}\n\nAdditionally, the following fields have been cleared from your profile:\n{{clearedFields}}", + "mark-as-read": "Mark as read", + "no-notifications": "You have no notifications." + } } diff --git a/Foxnouns.Frontend/src/lib/index.ts b/Foxnouns.Frontend/src/lib/index.ts index fdf8885..42d3314 100644 --- a/Foxnouns.Frontend/src/lib/index.ts +++ b/Foxnouns.Frontend/src/lib/index.ts @@ -9,9 +9,10 @@ export const setToken = (cookies: Cookies, token: string) => cookies.set(TOKEN_COOKIE_NAME, token, { path: "/" }); export const clearToken = (cookies: Cookies) => cookies.delete(TOKEN_COOKIE_NAME, { path: "/" }); -// TODO: change this to something we actually clearly have the rights to use -export const DEFAULT_AVATAR = "https://pronouns.cc/default/512.webp"; export const DEFAULT_FLAG = "/unknown_flag.svg"; export const idTimestamp = (id: string) => DateTime.fromMillis(parseInt(id, 10) / (1 << 22) + 1_640_995_200_000); + +export const alertKey = (url: URL): string | undefined => + url.searchParams.has("alert") ? "alert." + url.searchParams.get("alert") : undefined; diff --git a/Foxnouns.Frontend/src/lib/markdown.ts b/Foxnouns.Frontend/src/lib/markdown.ts index 94a1a05..9c4ff35 100644 --- a/Foxnouns.Frontend/src/lib/markdown.ts +++ b/Foxnouns.Frontend/src/lib/markdown.ts @@ -7,11 +7,7 @@ const md = new MarkdownIt({ linkify: true, }).disable(["heading", "lheading", "link", "table", "blockquote"]); -const unsafeMd = new MarkdownIt({ - html: false, - breaks: true, - linkify: true, -}); +const unsafeMd = new MarkdownIt(); export const renderMarkdown = (src: string | null) => (src ? sanitize(md.render(src)) : null); diff --git a/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts b/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts new file mode 100644 index 0000000..5f45815 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/pageUtils.svelte.ts @@ -0,0 +1,10 @@ +import { page } from "$app/state"; + +export const isActive = (path: string | string[], prefix: boolean = false) => + typeof path === "string" + ? prefix + ? page.url.pathname.startsWith(path) + : page.url.pathname === path + : prefix + ? path.some((p) => page.url.pathname.startsWith(p)) + : path.some((p) => page.url.pathname === p); diff --git a/Foxnouns.Frontend/src/lib/state.svelte.ts b/Foxnouns.Frontend/src/lib/state.svelte.ts new file mode 100644 index 0000000..8358bf3 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/state.svelte.ts @@ -0,0 +1,37 @@ +import { onMount, onDestroy } from "svelte"; +import { browser } from "$app/environment"; +import log from "./log"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { Snapshot } from "@sveltejs/kit"; + +/** + * Store ephemeral state in sessionStorage to persist between navigations. + * Similar to {@link Snapshot}, but doesn't attach it to a history entry. + * @param key Unique key to use for this state. + * @param capture Function that returns the state to store. + * @param restore Function that takes the state that was stored previously and assigns it back to component variables. + */ +export default function ephemeralState( + key: string, + capture: () => T, + restore: (data: T) => void, +): void { + if (!browser) return; + + onMount(() => { + if (!("sessionStorage" in window)) return; + const rawData = sessionStorage.getItem("ephemeral-" + key); + if (!rawData) return; + + log.debug("Restoring data %s from session storage", key); + const data = JSON.parse(rawData) as T; + restore(data); + }); + + onDestroy(() => { + if (!("sessionStorage" in window)) return; + + log.debug("Saving data %s to session storage", key); + sessionStorage.setItem("ephemeral-" + key, JSON.stringify(capture())); + }); +} diff --git a/Foxnouns.Frontend/src/routes/+error.svelte b/Foxnouns.Frontend/src/routes/+error.svelte index ca116b9..e667611 100644 --- a/Foxnouns.Frontend/src/routes/+error.svelte +++ b/Foxnouns.Frontend/src/routes/+error.svelte @@ -1,5 +1,6 @@ - - -{@render children?.()} +
+
+ + {@render children?.()} +
+
+
diff --git a/Foxnouns.Frontend/src/routes/+page.svelte b/Foxnouns.Frontend/src/routes/+page.svelte index 47ab0e5..4fd36dc 100644 --- a/Foxnouns.Frontend/src/routes/+page.svelte +++ b/Foxnouns.Frontend/src/routes/+page.svelte @@ -1,4 +1,5 @@ + + diff --git a/Foxnouns.Frontend/src/routes/admin/+page.svelte b/Foxnouns.Frontend/src/routes/admin/+page.svelte new file mode 100644 index 0000000..bf0c8f7 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/+page.svelte @@ -0,0 +1,27 @@ + + + + Admin dashboard • pronouns.cc + + +

Dashboard

+ +
+ + {data.meta.users.total.toLocaleString("en")} +
+ ({data.meta.users.active_month.toLocaleString("en")} active in the last month) +
+ {data.meta.members.toLocaleString("en")} + + {data.reportCount.toLocaleString("en")} +
+ ({data.staleReportCount} older than 1 week) +
+
diff --git a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts new file mode 100644 index 0000000..f48e334 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.server.ts @@ -0,0 +1,38 @@ +import { apiRequest } from "$api"; +import { type AuditLogEntity, type AuditLogEntry } from "$api/models/moderation.js"; + +export const load = async ({ url, fetch, cookies }) => { + const type = url.searchParams.get("type"); + const before = url.searchParams.get("before"); + const after = url.searchParams.get("after"); + const byModerator = url.searchParams.get("by-moderator"); + let limit: number = 100; + if (url.searchParams.has("limit")) limit = parseInt(url.searchParams.get("limit")!); + + const params = new URLSearchParams(); + params.set("limit", limit.toString()); + if (type) params.set("type", type); + if (before) params.set("before", before); + if (after) params.set("after", after); + if (byModerator) params.set("by-moderator", byModerator); + + const entries = await apiRequest( + "GET", + `/moderation/audit-log?${params.toString()}`, + { + fetch, + cookies, + }, + ); + + const moderators = await apiRequest("GET", "/moderation/audit-log/moderators", { + fetch, + cookies, + }); + + let modFilter: AuditLogEntity | null = null; + if (byModerator) + modFilter = entries.find((e) => e.moderator.id === byModerator)?.moderator || null; + + return { entries, type, before, after, modFilter, url: url.toString(), moderators }; +}; diff --git a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte new file mode 100644 index 0000000..0c1cf72 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte @@ -0,0 +1,115 @@ + + + + Audit log • pronouns.cc + + +

Audit log

+ +
+ + + Filter by type + + + + Ignore report + + + Warn user + + + Warn user and clear profile + + + Suspend user + + + Query sensitive user data + + {#if data.type} + Remove filter + {/if} + + + + + Filter by moderator + + + {#each data.moderators as mod (mod.id)} + + {mod.username} + + {/each} + {#if data.modFilter} + Remove filter + {/if} + + +
+ +{#if data.before} + Show newer entries +{/if} + +{#each data.entries as entry (entry.id)} + +{:else} +

There are no entries matching your filter

+{/each} + +{#if data.entries.length === 100} + Show older entries +{/if} diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts new file mode 100644 index 0000000..90a284e --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts @@ -0,0 +1,27 @@ +import { apiRequest } from "$api"; +import { redirect } from "@sveltejs/kit"; + +export const actions = { + default: async ({ request, fetch, cookies }) => { + const body = await request.formData(); + const query = body.get("query") as string; + const fuzzy = body.get("fuzzy") === "yes"; + + const users = await apiRequest>( + "POST", + "/moderation/lookup", + { + fetch, + cookies, + body: { + query, + fuzzy, + }, + }, + ); + + if (!fuzzy && users.length > 0) redirect(303, `/admin/lookup/${users[0].id}`); + + return { users }; + }, +}; diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte b/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte new file mode 100644 index 0000000..b3c8a8d --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte @@ -0,0 +1,33 @@ + + + + Look up a user • pronouns.cc + + +

Look up a user

+ +
+
+ + +
+
+ + +
+
+ +
+ {#each form?.users || [] as user (user.id)} + + {user.username} ({user.id}) + + {:else} +
No results
+ {/each} +
diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts new file mode 100644 index 0000000..130ae90 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts @@ -0,0 +1,11 @@ +import { apiRequest } from "$api"; +import type { QueriedUser } from "$api/models/moderation"; + +export const load = async ({ params, fetch, cookies }) => { + const user = await apiRequest("GET", `/moderation/lookup/${params.id}`, { + fetch, + cookies, + }); + + return { user }; +}; diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte new file mode 100644 index 0000000..e3cf389 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte @@ -0,0 +1,63 @@ + + + + Looking up @{data.user.user.username} • pronouns.cc + + +

Basic profile

+ + + + + +

Extra information

+ + + + + + + + + + + + + + + + +
Created at{createdAt.toLocaleString(DateTime.DATETIME_MED)}
Last active{lastActive.toLocaleString(DateTime.DATETIME_MED)}
Last SID reroll{lastSidReroll.toLocaleString(DateTime.DATETIME_MED)}
+ +{#if authMethods} +

Authentication methods

+
+ {#each authMethods as method (method.id)} + + {/each} +
+{/if} diff --git a/Foxnouns.Frontend/src/routes/admin/reports/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/reports/+page.server.ts new file mode 100644 index 0000000..c2149c1 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/reports/+page.server.ts @@ -0,0 +1,23 @@ +import { apiRequest } from "$api"; +import type { Report } from "$api/models/moderation"; + +export const load = async ({ url, fetch, cookies }) => { + const before = url.searchParams.get("before"); + const after = url.searchParams.get("after"); + const byReporter = url.searchParams.get("by-reporter"); + const byTarget = url.searchParams.get("by-target"); + const includeClosed = url.searchParams.get("include-closed") === "true"; + + const params = new URLSearchParams(); + if (before) params.set("before", before); + if (after) params.set("after", after); + if (byReporter) params.set("by-reporter", byReporter); + if (byTarget) params.set("by-target", byTarget); + if (includeClosed) params.set("include-closed", "true"); + + const reports = await apiRequest("GET", `/moderation/reports?${params.toString()}`, { + fetch, + cookies, + }); + return { reports, url: url.toString(), includeClosed, byReporter, byTarget, before, after }; +}; diff --git a/Foxnouns.Frontend/src/routes/admin/reports/+page.svelte b/Foxnouns.Frontend/src/routes/admin/reports/+page.svelte new file mode 100644 index 0000000..5021846 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/reports/+page.svelte @@ -0,0 +1,138 @@ + + + + Reports • pronouns.cc + + +

Reports

+ +
    + {#if data.byTarget} +
  • Filtering by target (clear)
  • + {/if} + {#if data.byReporter} +
  • Filtering by reporter (clear)
  • + {/if} + {#if data.includeClosed} +
  • Showing all reports (only show open reports)
  • + {:else} +
  • Showing open reports (show all reports)
  • + {/if} +
+ +{#if data.before} + Show newer reports +{/if} + + + + + + + + + + + + + + + {#each data.reports as report (report.id)} + + + + + + + + + + {/each} + +
UserMemberReporterReasonContext?Created at
+ + + Open report + + + @{report.target_user.username} + ( + {#if data.byTarget === report.target_user.id}{:else}{/if} + ) + + {#if report.target_member} + + {report.target_member.name} + + {:else} + (none) + {/if} + + {report.reporter.username} + ( + {#if data.byReporter === report.reporter.id}{:else}{/if} + ) + {report.reason} + {#if report.context} + {$t("yes")} + {:else} + {$t("no")} + {/if} + + {idTimestamp(report.id).toLocaleString(DateTime.DATETIME_SHORT)} +
+ +{#if data.reports.length === 100} + Show older reports +{/if} diff --git a/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.server.ts new file mode 100644 index 0000000..214049a --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.server.ts @@ -0,0 +1,18 @@ +import { apiRequest } from "$api"; +import type { ReportDetails } from "$api/models/moderation"; +import { createModactions } from "$lib/actions/modaction"; + +export const load = async ({ params, fetch, cookies }) => { + const resp = await apiRequest("GET", `/moderation/reports/${params.id}`, { + fetch, + cookies, + }); + return { + report: resp.report, + user: resp.user, + member: resp.member, + auditLogEntry: resp.audit_log_entry, + }; +}; + +export const actions = createModactions(); diff --git a/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.svelte new file mode 100644 index 0000000..8a1ae08 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/reports/[id]/+page.svelte @@ -0,0 +1,99 @@ + + + + Report on @{user.username} • pronouns.cc + + +{#if report.status === "CLOSED"} +
+ This report has already been handled. See audit log entry +
+{/if} + +
+
+

Target user

+ +
+ {#if report.target_member} +
+

Target member

+ +
+ {/if} +
+

Reporter

+ +
+
+ +
+
+

Reason

+

{report.reason}

+
+
+

Context

+

+ {#if report.context} + + {@html renderMarkdown(report.context)} + {:else} + (no context given) + {/if} +

+
+
+ +{#if report.status === "OPEN"} +
+

Take action

+ +
+{:else if report.status === "CLOSED" && auditLogEntry} + +{:else} +
+

Closed by an unknown moderator

+

+ This should not happen! +

+
+{/if} + +{#if report.snapshot} +

Profile at time of report

+
+ {#if report.target_type === "USER"} + {@const snapshot = report.snapshot as User} + + {:else} + {@const snapshot = report.snapshot as Member} + + {/if} +{/if} diff --git a/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.server.ts index dc66812..b297e25 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.server.ts @@ -1,8 +1,3 @@ import createCallbackLoader from "$lib/actions/callback"; -import createRegisterAction from "$lib/actions/register"; export const load = createCallbackLoader("discord"); - -export const actions = { - default: createRegisterAction("/auth/discord/register"), -}; diff --git a/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.svelte b/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.svelte index e82f0f1..10fec06 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.svelte +++ b/Foxnouns.Frontend/src/routes/auth/callback/discord/+page.svelte @@ -1,12 +1,11 @@ @@ -17,15 +16,7 @@ {#if data.error}

{$t("auth.register-with-discord")}

- {:else if data.isLinkRequest} - {:else} - + {/if}
diff --git a/Foxnouns.Frontend/src/routes/auth/callback/google/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/callback/google/+page.server.ts index 49f963c..d3e1b93 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/google/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/callback/google/+page.server.ts @@ -1,8 +1,3 @@ import createCallbackLoader from "$lib/actions/callback"; -import createRegisterAction from "$lib/actions/register"; export const load = createCallbackLoader("google"); - -export const actions = { - default: createRegisterAction("/auth/google/register"), -}; diff --git a/Foxnouns.Frontend/src/routes/auth/callback/google/+page.svelte b/Foxnouns.Frontend/src/routes/auth/callback/google/+page.svelte index 284806a..eeff070 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/google/+page.svelte +++ b/Foxnouns.Frontend/src/routes/auth/callback/google/+page.svelte @@ -1,12 +1,11 @@ @@ -17,15 +16,7 @@ {#if data.error}

{$t("auth.register-with-google")}

- {:else if data.isLinkRequest} - {:else} - + {/if}
diff --git a/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.server.ts index b092b1e..c2d290e 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.server.ts @@ -1,6 +1,5 @@ import ApiError, { ErrorCode } from "$api/error"; import createCallbackLoader from "$lib/actions/callback"; -import createRegisterAction from "$lib/actions/register"; export const load = createCallbackLoader("fediverse", async ({ params, url }) => { const code = url.searchParams.get("code") as string | null; @@ -10,7 +9,3 @@ export const load = createCallbackLoader("fediverse", async ({ params, url }) => return { code: code || token, state, instance: params.instance! }; }); - -export const actions = { - default: createRegisterAction("/auth/fediverse/register"), -}; diff --git a/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.svelte b/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.svelte index 99bd00c..fdc8d7a 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.svelte +++ b/Foxnouns.Frontend/src/routes/auth/callback/mastodon/[instance]/+page.svelte @@ -1,12 +1,11 @@ @@ -17,15 +16,7 @@ {#if data.error}

{$t("auth.register-with-mastodon")}

- {:else if data.isLinkRequest} - {:else} - + {/if} diff --git a/Foxnouns.Frontend/src/lib/actions/register.ts b/Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.server.ts similarity index 62% rename from Foxnouns.Frontend/src/lib/actions/register.ts rename to Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.server.ts index d3c126d..b14c1a2 100644 --- a/Foxnouns.Frontend/src/lib/actions/register.ts +++ b/Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.server.ts @@ -3,10 +3,23 @@ import ApiError, { ErrorCode, type RawApiError } from "$api/error"; import type { AuthResponse } from "$api/models/auth"; import { setToken } from "$lib"; import log from "$lib/log"; -import { isRedirect, redirect, type RequestEvent } from "@sveltejs/kit"; +import { isRedirect, redirect } from "@sveltejs/kit"; + +export type TicketData = { + type: string; + ticket: string; + remoteUsername: string; +}; + +export const load = async ({ params }) => { + const data = JSON.parse(atob(params.ticket)) as TicketData; + return data; +}; + +export const actions = { + default: async ({ request, fetch, cookies, params }) => { + const type = (JSON.parse(atob(params.ticket)) as TicketData).type; -export default function createRegisterAction(callbackUrl: string) { - return async function ({ request, fetch, cookies }: RequestEvent) { const data = await request.formData(); const username = data.get("username") as string | null; const ticket = data.get("ticket") as string | null; @@ -17,7 +30,7 @@ export default function createRegisterAction(callbackUrl: string) { }; try { - const resp = await apiRequest("POST", callbackUrl, { + const resp = await apiRequest("POST", `/auth/${type}/register`, { body: { username, ticket }, isInternal: true, fetch, @@ -31,5 +44,5 @@ export default function createRegisterAction(callbackUrl: string) { if (e instanceof ApiError) return { error: e.obj }; throw e; } - }; -} + }, +}; diff --git a/Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.svelte b/Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.svelte new file mode 100644 index 0000000..6d1b6fc --- /dev/null +++ b/Foxnouns.Frontend/src/routes/auth/callback/register/[ticket]/+page.svelte @@ -0,0 +1,22 @@ + + + + {$t(`auth.register-with-${data.type}`)} • pronouns.cc + + +
+ +
diff --git a/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.server.ts index 49346f1..813f19e 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.server.ts @@ -1,8 +1,3 @@ import createCallbackLoader from "$lib/actions/callback"; -import createRegisterAction from "$lib/actions/register"; export const load = createCallbackLoader("tumblr"); - -export const actions = { - default: createRegisterAction("/auth/tumblr/register"), -}; diff --git a/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.svelte b/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.svelte index c7c53e9..7a43261 100644 --- a/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.svelte +++ b/Foxnouns.Frontend/src/routes/auth/callback/tumblr/+page.svelte @@ -1,12 +1,11 @@ @@ -17,15 +16,7 @@ {#if data.error}

{$t("auth.register-with-tumblr")}

- {:else if data.isLinkRequest} - {:else} - + {/if} diff --git a/Foxnouns.Frontend/src/routes/auth/log-in/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/log-in/+page.server.ts index 9c6bf25..579ea67 100644 --- a/Foxnouns.Frontend/src/routes/auth/log-in/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/log-in/+page.server.ts @@ -2,15 +2,15 @@ import { isRedirect, redirect } from "@sveltejs/kit"; import { apiRequest } from "$api"; import type { AuthResponse, AuthUrls } from "$api/models/auth"; -import { setToken } from "$lib"; +import { alertKey, setToken } from "$lib"; import ApiError, { ErrorCode } from "$api/error"; -export const load = async ({ fetch, parent }) => { +export const load = async ({ fetch, parent, url }) => { const parentData = await parent(); if (parentData.meUser) redirect(303, `/@${parentData.meUser.username}`); const urls = await apiRequest("POST", "/auth/urls", { fetch, isInternal: true }); - return { urls }; + return { urls, alertKey: alertKey(url) }; }; export const actions = { diff --git a/Foxnouns.Frontend/src/routes/auth/log-in/+page.svelte b/Foxnouns.Frontend/src/routes/auth/log-in/+page.svelte index c6c47a9..3efbfa0 100644 --- a/Foxnouns.Frontend/src/routes/auth/log-in/+page.svelte +++ b/Foxnouns.Frontend/src/routes/auth/log-in/+page.svelte @@ -3,6 +3,7 @@ import { t } from "$lib/i18n"; import { enhance } from "$app/forms"; import ErrorAlert from "$components/ErrorAlert.svelte"; + import UrlAlert from "$components/URLAlert.svelte"; type Props = { data: PageData; form: ActionData }; let { data, form }: Props = $props(); @@ -13,6 +14,7 @@
+
{#if form?.error} @@ -48,8 +50,13 @@
{/if}
-

{$t("auth.log-in-3rd-party-header")}

-

{$t("auth.log-in-3rd-party-desc")}

+ {#if data.urls.email_enabled} +

{$t("auth.log-in-3rd-party-header")}

+

{$t("auth.log-in-3rd-party-desc")}

+ {:else} +

{$t("title.log-in")}

+

{$t("auth.log-in-3rd-party-desc-no-email")}

+ {/if}
{#if data.urls.discord} diff --git a/Foxnouns.Frontend/src/routes/auth/welcome/+page.server.ts b/Foxnouns.Frontend/src/routes/auth/welcome/+page.server.ts index 88baf97..4b76df2 100644 --- a/Foxnouns.Frontend/src/routes/auth/welcome/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/auth/welcome/+page.server.ts @@ -2,5 +2,5 @@ import { redirect } from "@sveltejs/kit"; export const load = async ({ parent }) => { const { meUser } = await parent(); - if (!meUser) redirect(303, "/auth/log-in"); + if (!meUser) redirect(303, "/auth/log-in?alert=auth-required"); }; diff --git a/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts b/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts new file mode 100644 index 0000000..1d9e8fc --- /dev/null +++ b/Foxnouns.Frontend/src/routes/page/[page]/+page.server.ts @@ -0,0 +1,14 @@ +import { baseRequest } from "$api"; +import ApiError from "$api/error"; + +export const load = async ({ fetch, params }) => { + const resp = await baseRequest("GET", `/meta/page/${params.page}`, { fetch }); + if (resp.status < 200 || resp.status > 299) { + const err = await resp.json(); + if ("code" in err) throw new ApiError(err); + else throw new ApiError(); + } + + const pageText = await resp.text(); + return { page: params.page, text: pageText }; +}; diff --git a/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte b/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte new file mode 100644 index 0000000..3af7b11 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/page/[page]/+page.svelte @@ -0,0 +1,23 @@ + + + + {title} • pronouns.cc + + +
+ + {@html md} +
diff --git a/Foxnouns.Frontend/src/routes/report/[id]/+page.server.ts b/Foxnouns.Frontend/src/routes/report/[id]/+page.server.ts new file mode 100644 index 0000000..5d36696 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/report/[id]/+page.server.ts @@ -0,0 +1,60 @@ +import { apiRequest, fastRequest } from "$api"; +import ApiError from "$api/error.js"; +import type { Member } from "$api/models/member.js"; +import { type CreateReportRequest, ReportReason } from "$api/models/moderation.js"; +import type { PartialUser, User } from "$api/models/user.js"; +import log from "$lib/log.js"; +import { redirect } from "@sveltejs/kit"; + +export const load = async ({ parent, params, fetch, cookies, url }) => { + const { meUser } = await parent(); + if (!meUser) redirect(303, "/"); + + let user: PartialUser; + let member: Member | null = null; + if (url.searchParams.has("member")) { + const resp = await apiRequest( + "GET", + `/users/${params.id}/members/${url.searchParams.get("member")}`, + { fetch, cookies }, + ); + + user = resp.user; + member = resp; + } else { + user = await apiRequest("GET", `/users/${params.id}`, { fetch, cookies }); + } + + if (meUser.id === user.id) redirect(303, "/"); + + return { user, member }; +}; + +export const actions = { + default: async ({ request, fetch, cookies }) => { + const body = await request.formData(); + + const targetIsMember = body.get("target-type") === "member"; + const target = body.get("target-id") as string; + const reason = body.get("reason") as ReportReason; + const context = body.get("context") as string | null; + + const url = targetIsMember + ? `/moderation/report-member/${target}` + : `/moderation/report-user/${target}`; + + try { + await fastRequest("POST", url, { + body: { reason, context }, + fetch, + cookies, + }); + + return { ok: true, error: null }; + } catch (e) { + if (e instanceof ApiError) return { ok: false, error: e.obj }; + log.error("error reporting user or member %s:", target, e); + throw e; + } + }, +}; diff --git a/Foxnouns.Frontend/src/routes/report/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/report/[id]/+page.svelte new file mode 100644 index 0000000..a4ce0ac --- /dev/null +++ b/Foxnouns.Frontend/src/routes/report/[id]/+page.svelte @@ -0,0 +1,70 @@ + + + + {$t("report.title", { name })} • pronouns.cc + + +
+ +

{$t("report.title", { name })}

+ + + + +

{$t("report.reason-label")}

+
+ {#each reasons as reason} +
+
+ + +
+
+ {/each} +
+ +

+ {$t("report.context-label")} + +

+ + +
+ + {$t("cancel")} +
+ +
diff --git a/Foxnouns.Frontend/src/routes/settings/+layout.server.ts b/Foxnouns.Frontend/src/routes/settings/+layout.server.ts index fe2eaa3..95228f0 100644 --- a/Foxnouns.Frontend/src/routes/settings/+layout.server.ts +++ b/Foxnouns.Frontend/src/routes/settings/+layout.server.ts @@ -2,7 +2,7 @@ import { redirect } from "@sveltejs/kit"; export const load = async ({ parent }) => { const data = await parent(); - if (!data.meUser) redirect(303, "/auth/log-in"); + if (!data.meUser) redirect(303, "/auth/log-in?alert=auth-required"); return { user: data.meUser!, token: data.token! }; }; diff --git a/Foxnouns.Frontend/src/routes/settings/+layout.svelte b/Foxnouns.Frontend/src/routes/settings/+layout.svelte index 6786e63..8d660ea 100644 --- a/Foxnouns.Frontend/src/routes/settings/+layout.svelte +++ b/Foxnouns.Frontend/src/routes/settings/+layout.svelte @@ -1,26 +1,30 @@ {$t("title.settings")} • pronouns.cc +{#if data.meta.notice} +
+ +
+{/if} +