From 24155a149ca12f8e07edd2b77ca84723e355310e Mon Sep 17 00:00:00 2001
From: sam
Date: Tue, 4 Jun 2024 17:43:48 +0200
Subject: [PATCH 001/261] fix: fix BuildInfo not being initialized
---
Foxnouns.Backend/Foxnouns.Backend.csproj | 4 ++++
Foxnouns.Backend/Program.cs | 3 +++
2 files changed, 7 insertions(+)
diff --git a/Foxnouns.Backend/Foxnouns.Backend.csproj b/Foxnouns.Backend/Foxnouns.Backend.csproj
index 8438390..f92814a 100644
--- a/Foxnouns.Backend/Foxnouns.Backend.csproj
+++ b/Foxnouns.Backend/Foxnouns.Backend.csproj
@@ -29,4 +29,8 @@
+
+
+
+
diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs
index 4eca8cb..5025f70 100644
--- a/Foxnouns.Backend/Program.cs
+++ b/Foxnouns.Backend/Program.cs
@@ -6,6 +6,9 @@ using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
+// Read version information from .version in the repository root
+await BuildInfo.ReadBuildInfo();
+
var builder = WebApplication.CreateBuilder(args);
var config = builder.AddConfiguration();
From 401e268281f4aacff2c9c94df3c8f291cc3981f0 Mon Sep 17 00:00:00 2001
From: sam
Date: Tue, 4 Jun 2024 17:47:16 +0200
Subject: [PATCH 002/261] chore: add back Properties/launchSettings.json
---
.../Properties/launchSettings.json | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 Foxnouns.Backend/Properties/launchSettings.json
diff --git a/Foxnouns.Backend/Properties/launchSettings.json b/Foxnouns.Backend/Properties/launchSettings.json
new file mode 100644
index 0000000..b680651
--- /dev/null
+++ b/Foxnouns.Backend/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Development": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "externalUrlConfiguration": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Production": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "externalUrlConfiguration": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production"
+ }
+ }
+ }
+}
\ No newline at end of file
From 14f8e77e6a0c7150d6cccc398a80114ab4738f9c Mon Sep 17 00:00:00 2001
From: sam
Date: Sat, 8 Jun 2024 21:02:12 +0200
Subject: [PATCH 003/261] add sveltekit template
---
.../.idea/codeStyles/codeStyleConfig.xml | 5 +
.../inspectionProfiles/Project_Default.xml | 6 +
.../.idea/jsLinters/eslint.xml | 6 +
.idea/.idea.Foxnouns.NET/.idea/prettier.xml | 9 +
.../Controllers/DebugController.cs | 2 +-
.../Controllers/MetaController.cs | 21 +
Foxnouns.Frontend/.env.example | 4 +
Foxnouns.Frontend/.gitignore | 10 +
Foxnouns.Frontend/.npmrc | 1 +
Foxnouns.Frontend/.prettierignore | 4 +
Foxnouns.Frontend/.prettierrc | 6 +
Foxnouns.Frontend/README.md | 38 +
Foxnouns.Frontend/eslint.config.js | 33 +
Foxnouns.Frontend/package.json | 36 +
Foxnouns.Frontend/src/app.d.ts | 16 +
Foxnouns.Frontend/src/app.html | 12 +
Foxnouns.Frontend/src/hooks.server.ts | 15 +
Foxnouns.Frontend/src/lib/index.ts | 1 +
Foxnouns.Frontend/src/routes/+page.svelte | 2 +
Foxnouns.Frontend/static/favicon.png | Bin 0 -> 1571 bytes
Foxnouns.Frontend/svelte.config.js | 21 +
Foxnouns.Frontend/tsconfig.json | 19 +
Foxnouns.Frontend/vite.config.ts | 6 +
Foxnouns.Frontend/yarn.lock | 1885 +++++++++++++++++
24 files changed, 2157 insertions(+), 1 deletion(-)
create mode 100644 .idea/.idea.Foxnouns.NET/.idea/codeStyles/codeStyleConfig.xml
create mode 100644 .idea/.idea.Foxnouns.NET/.idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/.idea.Foxnouns.NET/.idea/jsLinters/eslint.xml
create mode 100644 .idea/.idea.Foxnouns.NET/.idea/prettier.xml
create mode 100644 Foxnouns.Backend/Controllers/MetaController.cs
create mode 100644 Foxnouns.Frontend/.env.example
create mode 100644 Foxnouns.Frontend/.gitignore
create mode 100644 Foxnouns.Frontend/.npmrc
create mode 100644 Foxnouns.Frontend/.prettierignore
create mode 100644 Foxnouns.Frontend/.prettierrc
create mode 100644 Foxnouns.Frontend/README.md
create mode 100644 Foxnouns.Frontend/eslint.config.js
create mode 100644 Foxnouns.Frontend/package.json
create mode 100644 Foxnouns.Frontend/src/app.d.ts
create mode 100644 Foxnouns.Frontend/src/app.html
create mode 100644 Foxnouns.Frontend/src/hooks.server.ts
create mode 100644 Foxnouns.Frontend/src/lib/index.ts
create mode 100644 Foxnouns.Frontend/src/routes/+page.svelte
create mode 100644 Foxnouns.Frontend/static/favicon.png
create mode 100644 Foxnouns.Frontend/svelte.config.js
create mode 100644 Foxnouns.Frontend/tsconfig.json
create mode 100644 Foxnouns.Frontend/vite.config.ts
create mode 100644 Foxnouns.Frontend/yarn.lock
diff --git a/.idea/.idea.Foxnouns.NET/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.Foxnouns.NET/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/.idea.Foxnouns.NET/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Foxnouns.NET/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.Foxnouns.NET/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/.idea.Foxnouns.NET/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Foxnouns.NET/.idea/jsLinters/eslint.xml b/.idea/.idea.Foxnouns.NET/.idea/jsLinters/eslint.xml
new file mode 100644
index 0000000..204acf7
--- /dev/null
+++ b/.idea/.idea.Foxnouns.NET/.idea/jsLinters/eslint.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Foxnouns.NET/.idea/prettier.xml b/.idea/.idea.Foxnouns.NET/.idea/prettier.xml
new file mode 100644
index 0000000..653a9e0
--- /dev/null
+++ b/.idea/.idea.Foxnouns.NET/.idea/prettier.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/DebugController.cs b/Foxnouns.Backend/Controllers/DebugController.cs
index 328bf3d..4746d95 100644
--- a/Foxnouns.Backend/Controllers/DebugController.cs
+++ b/Foxnouns.Backend/Controllers/DebugController.cs
@@ -28,5 +28,5 @@ public class DebugController(DatabaseContext db, AuthService authSvc, IClock clo
public record CreateUserRequest(string Username, string Password, string Email);
- public record AuthResponse(Snowflake Id, string Username, string Token);
+ private record AuthResponse(Snowflake Id, string Username, string Token);
}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs
new file mode 100644
index 0000000..d43749e
--- /dev/null
+++ b/Foxnouns.Backend/Controllers/MetaController.cs
@@ -0,0 +1,21 @@
+using Foxnouns.Backend.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Foxnouns.Backend.Controllers;
+
+[Route("/api/v2/meta")]
+public class MetaController(DatabaseContext db) : ApiControllerBase
+{
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MetaResponse))]
+ public async Task GetMeta()
+ {
+ var userCount = await db.Users.CountAsync();
+ var memberCount = await db.Members.CountAsync();
+
+ return Ok(new MetaResponse(userCount, memberCount, BuildInfo.Version, BuildInfo.Hash));
+ }
+
+ private record MetaResponse(int Users, int Members, string Version, string Hash);
+}
\ No newline at end of file
diff --git a/Foxnouns.Frontend/.env.example b/Foxnouns.Frontend/.env.example
new file mode 100644
index 0000000..91687be
--- /dev/null
+++ b/Foxnouns.Frontend/.env.example
@@ -0,0 +1,4 @@
+# The API base that the server itself should call, this should not be behind a reverse proxy.
+PRIVATE_API_BASE=http://localhost:5000/api
+# The API base that clients should call, behind a reverse proxy.
+PUBLIC_API_BASE=https://pronouns.cc/api
diff --git a/Foxnouns.Frontend/.gitignore b/Foxnouns.Frontend/.gitignore
new file mode 100644
index 0000000..6635cf5
--- /dev/null
+++ b/Foxnouns.Frontend/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
diff --git a/Foxnouns.Frontend/.npmrc b/Foxnouns.Frontend/.npmrc
new file mode 100644
index 0000000..b6f27f1
--- /dev/null
+++ b/Foxnouns.Frontend/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/Foxnouns.Frontend/.prettierignore b/Foxnouns.Frontend/.prettierignore
new file mode 100644
index 0000000..cc41cea
--- /dev/null
+++ b/Foxnouns.Frontend/.prettierignore
@@ -0,0 +1,4 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
diff --git a/Foxnouns.Frontend/.prettierrc b/Foxnouns.Frontend/.prettierrc
new file mode 100644
index 0000000..9b685c1
--- /dev/null
+++ b/Foxnouns.Frontend/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "useTabs": true,
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-svelte"],
+ "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
+}
diff --git a/Foxnouns.Frontend/README.md b/Foxnouns.Frontend/README.md
new file mode 100644
index 0000000..5ce6766
--- /dev/null
+++ b/Foxnouns.Frontend/README.md
@@ -0,0 +1,38 @@
+# create-svelte
+
+Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
+
+## Creating a project
+
+If you're seeing this, you've probably already done this step. Congrats!
+
+```bash
+# create a new project in the current directory
+npm create svelte@latest
+
+# create a new project in my-app
+npm create svelte@latest my-app
+```
+
+## Developing
+
+Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
+
+```bash
+npm run dev
+
+# or start the server and open the app in a new browser tab
+npm run dev -- --open
+```
+
+## Building
+
+To create a production version of your app:
+
+```bash
+npm run build
+```
+
+You can preview the production build with `npm run preview`.
+
+> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
diff --git a/Foxnouns.Frontend/eslint.config.js b/Foxnouns.Frontend/eslint.config.js
new file mode 100644
index 0000000..a351fa9
--- /dev/null
+++ b/Foxnouns.Frontend/eslint.config.js
@@ -0,0 +1,33 @@
+import js from '@eslint/js';
+import ts from 'typescript-eslint';
+import svelte from 'eslint-plugin-svelte';
+import prettier from 'eslint-config-prettier';
+import globals from 'globals';
+
+/** @type {import('eslint').Linter.FlatConfig[]} */
+export default [
+ js.configs.recommended,
+ ...ts.configs.recommended,
+ ...svelte.configs['flat/recommended'],
+ prettier,
+ ...svelte.configs['flat/prettier'],
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node
+ }
+ }
+ },
+ {
+ files: ['**/*.svelte'],
+ languageOptions: {
+ parserOptions: {
+ parser: ts.parser
+ }
+ }
+ },
+ {
+ ignores: ['build/', '.svelte-kit/', 'dist/']
+ }
+];
diff --git a/Foxnouns.Frontend/package.json b/Foxnouns.Frontend/package.json
new file mode 100644
index 0000000..fb3ce1f
--- /dev/null
+++ b/Foxnouns.Frontend/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "foxnouns.frontend",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "lint": "prettier --check . && eslint .",
+ "format": "prettier --write ."
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-node": "^5.0.1",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "@sveltestrap/sveltestrap": "^6.2.7",
+ "@types/eslint": "^8.56.7",
+ "bootstrap": "^5.3.3",
+ "eslint": "^9.0.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-svelte": "^2.36.0",
+ "globals": "^15.0.0",
+ "prettier": "^3.1.1",
+ "prettier-plugin-svelte": "^3.1.2",
+ "svelte": "^4.2.7",
+ "svelte-check": "^3.6.0",
+ "tslib": "^2.4.1",
+ "typescript": "^5.0.0",
+ "typescript-eslint": "^8.0.0-alpha.20",
+ "vite": "^5.0.3"
+ },
+ "type": "module",
+ "dependencies": {}
+}
diff --git a/Foxnouns.Frontend/src/app.d.ts b/Foxnouns.Frontend/src/app.d.ts
new file mode 100644
index 0000000..f7864c3
--- /dev/null
+++ b/Foxnouns.Frontend/src/app.d.ts
@@ -0,0 +1,16 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ interface Locals {
+ token?: string;
+ }
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
+}
+
+export {};
diff --git a/Foxnouns.Frontend/src/app.html b/Foxnouns.Frontend/src/app.html
new file mode 100644
index 0000000..77a5ff5
--- /dev/null
+++ b/Foxnouns.Frontend/src/app.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/Foxnouns.Frontend/src/hooks.server.ts b/Foxnouns.Frontend/src/hooks.server.ts
new file mode 100644
index 0000000..59efc4b
--- /dev/null
+++ b/Foxnouns.Frontend/src/hooks.server.ts
@@ -0,0 +1,15 @@
+import { PRIVATE_API_BASE } from "$env/static/private";
+import { PUBLIC_API_BASE } from "$env/static/public";
+
+export async function handle({ event, resolve }) {
+ event.locals.token = event.cookies.get("pronounscc-token");
+ return await resolve(event);
+}
+
+export function handleFetch({ event, request, fetch }) {
+ if (request.url.startsWith(PUBLIC_API_BASE))
+ request = new Request(request.url.replace(PUBLIC_API_BASE, PRIVATE_API_BASE), request);
+ if (event.locals.token) request.headers.set("Authorization", event.locals.token);
+
+ return fetch(request);
+}
diff --git a/Foxnouns.Frontend/src/lib/index.ts b/Foxnouns.Frontend/src/lib/index.ts
new file mode 100644
index 0000000..856f2b6
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/index.ts
@@ -0,0 +1 @@
+// place files you want to import through the `$lib` alias in this folder.
diff --git a/Foxnouns.Frontend/src/routes/+page.svelte b/Foxnouns.Frontend/src/routes/+page.svelte
new file mode 100644
index 0000000..5982b0a
--- /dev/null
+++ b/Foxnouns.Frontend/src/routes/+page.svelte
@@ -0,0 +1,2 @@
+Welcome to SvelteKit
+Visit kit.svelte.dev to read the documentation
diff --git a/Foxnouns.Frontend/static/favicon.png b/Foxnouns.Frontend/static/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097
GIT binary patch
literal 1571
zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N
z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z
zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH
z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;|
zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f
z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb
zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1
z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe
ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2`
zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F
z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL
z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi
z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y
zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA
zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6
zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6
zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5
zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH=0.36.0 <1.0.0"
+
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-scope@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc"
+ integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint-visitor-keys@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
+ integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
+
+eslint@^9.0.0:
+ version "9.4.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.4.0.tgz#79150c3610ae606eb131f1d648d5f43b3d45f3cd"
+ integrity sha512-sjc7Y8cUD1IlwYcTS9qPSvGjAC8Ne9LctpxKKu3x/1IC9bnOg98Zy6GxEJUfr1NojMgVPlyANXYns8oE2c1TAA==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/config-array" "^0.15.1"
+ "@eslint/eslintrc" "^3.1.0"
+ "@eslint/js" "9.4.0"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@humanwhocodes/retry" "^0.3.0"
+ "@nodelib/fs.walk" "^1.2.8"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^8.0.1"
+ eslint-visitor-keys "^4.0.0"
+ espree "^10.0.1"
+ esquery "^1.4.2"
+ 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"
+ is-path-inside "^3.0.3"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+esm-env@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413"
+ integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==
+
+espree@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
+ integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==
+ dependencies:
+ acorn "^8.11.3"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^4.0.0"
+
+espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
+esquery@^1.4.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+ integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+estree-walker@^3.0.0, estree-walker@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+esutils@^2.0.2, esutils@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.2.7, fast-glob@^3.2.9:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
+ integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
+ 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.4"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fastq@^1.6.0:
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
+ integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
+ dependencies:
+ reusify "^1.0.4"
+
+file-entry-cache@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
+ integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
+ dependencies:
+ flat-cache "^4.0.0"
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat-cache@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
+ integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.4"
+
+flatted@^3.2.9:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
+ integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+glob-parent@^5.1.2, glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^8.0.3:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+ integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^5.0.1"
+ once "^1.3.0"
+
+globals@^14.0.0:
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
+ integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
+
+globals@^15.0.0:
+ version "15.4.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-15.4.0.tgz#3e36ea6e4d9ddcf1cb42d92f5c4a145a8a2ddc1c"
+ integrity sha512-unnwvMZpv0eDUyjNyh9DH/yxUaRYrEjW/qK4QcdrHg3oO11igUQrCSgODHEqxlKg8v2CD2Sd7UkqqEBoz5U7TQ==
+
+globalyzer@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
+ integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
+
+globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
+globrex@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
+ integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
+
+graceful-fs@^4.1.3:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+hasown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+ignore@^5.2.0, ignore@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
+ integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
+
+import-fresh@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+import-meta-resolve@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
+ integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-builtin-module@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
+ integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
+ dependencies:
+ builtin-modules "^3.3.0"
+
+is-core-module@^2.13.0:
+ version "2.13.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
+ integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
+ dependencies:
+ hasown "^2.0.0"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-reference@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
+ integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
+ dependencies:
+ "@types/estree" "*"
+
+is-reference@^3.0.0, is-reference@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c"
+ integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==
+ dependencies:
+ "@types/estree" "*"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+keyv@^4.5.4:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+kleur@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
+ integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
+
+known-css-properties@^0.31.0:
+ version "0.31.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.31.0.tgz#5c8d9d8777b3ca09482b2397f6a241e5d69a1023"
+ integrity sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+lilconfig@^2.0.5:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
+ integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
+
+locate-character@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-3.0.0.tgz#0305c5b8744f61028ef5d01f444009e00779f974"
+ integrity sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+magic-string@^0.30.10, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.5:
+ version "0.30.10"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
+ integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.4.15"
+
+mdn-data@2.0.30:
+ version "2.0.30"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
+ integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@^4.0.4:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
+ integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+min-indent@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
+ integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
+
+minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^5.0.1:
+ version "5.1.6"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
+ integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^9.0.4:
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
+ integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimist@^1.2.0, minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mkdirp@^0.5.1:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+mri@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
+ integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
+
+mrmime@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4"
+ integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ 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"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+periscopic@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a"
+ integrity sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-walker "^3.0.0"
+ is-reference "^3.0.0"
+
+picocolors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
+ integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+postcss-load-config@^3.1.4:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
+ integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
+ dependencies:
+ lilconfig "^2.0.5"
+ yaml "^1.10.2"
+
+postcss-safe-parser@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1"
+ integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==
+
+postcss-scss@^4.0.9:
+ version "4.0.9"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685"
+ integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==
+
+postcss-selector-parser@^6.0.16:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53"
+ integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss@^8.4.38:
+ version "8.4.38"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
+ integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.0"
+ source-map-js "^1.2.0"
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier-plugin-svelte@^3.1.2:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.4.tgz#50366d550b2fe64b736ec0c90998805cfc395a2c"
+ integrity sha512-tZv+ADfeOWFNQkXkRh6zUXE16w3Vla8x2Ug0B/EnSmjR4EnwdwZbGgL/liSwR1kcEALU5mAAyua98HBxheCxgg==
+
+prettier@^3.1.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac"
+ integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==
+
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve@^1.22.1:
+ version "1.22.8"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
+ integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^2.5.2:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+ integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+ dependencies:
+ glob "^7.1.3"
+
+rollup@^4.13.0, rollup@^4.9.5:
+ version "4.18.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda"
+ integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==
+ dependencies:
+ "@types/estree" "1.0.5"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.18.0"
+ "@rollup/rollup-android-arm64" "4.18.0"
+ "@rollup/rollup-darwin-arm64" "4.18.0"
+ "@rollup/rollup-darwin-x64" "4.18.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.18.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.18.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.18.0"
+ "@rollup/rollup-linux-arm64-musl" "4.18.0"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.18.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.18.0"
+ "@rollup/rollup-linux-x64-gnu" "4.18.0"
+ "@rollup/rollup-linux-x64-musl" "4.18.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.18.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.18.0"
+ "@rollup/rollup-win32-x64-msvc" "4.18.0"
+ fsevents "~2.3.2"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+sade@^1.7.4, sade@^1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
+ integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
+ dependencies:
+ mri "^1.1.0"
+
+sander@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad"
+ integrity sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==
+ dependencies:
+ es6-promise "^3.1.2"
+ graceful-fs "^4.1.3"
+ mkdirp "^0.5.1"
+ rimraf "^2.5.2"
+
+semver@^7.5.4, semver@^7.6.0:
+ version "7.6.2"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
+ integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
+
+set-cookie-parser@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51"
+ integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+sirv@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0"
+ integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==
+ dependencies:
+ "@polka/url" "^1.0.0-next.24"
+ mrmime "^2.0.0"
+ totalist "^3.0.0"
+
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+sorcery@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.11.0.tgz#310c80ee993433854bb55bb9aa4003acd147fca8"
+ integrity sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+ buffer-crc32 "^0.2.5"
+ minimist "^1.2.0"
+ sander "^0.5.0"
+
+source-map-js@^1.0.1, source-map-js@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
+ integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+
+strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-indent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
+ integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
+ dependencies:
+ min-indent "^1.0.0"
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+svelte-check@^3.6.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-3.8.0.tgz#e0850b876d3d32760465bfb26d06b32c4c9f98a1"
+ integrity sha512-7Nxn+3X97oIvMzYJ7t27w00qUf1Y52irE2RU2dQAd5PyvfGp4E7NLhFKVhb6PV2fx7dCRMpNKDIuazmGthjpSQ==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ chokidar "^3.4.1"
+ fast-glob "^3.2.7"
+ import-fresh "^3.2.1"
+ picocolors "^1.0.0"
+ sade "^1.7.4"
+ svelte-preprocess "^5.1.3"
+ typescript "^5.0.3"
+
+"svelte-eslint-parser@>=0.36.0 <1.0.0":
+ version "0.36.0"
+ resolved "https://registry.yarnpkg.com/svelte-eslint-parser/-/svelte-eslint-parser-0.36.0.tgz#5390d86181180f2707c374b33c7d2fe42c1e1be2"
+ integrity sha512-/6YmUSr0FAVxW8dXNdIMydBnddPMHzaHirAZ7RrT21XYdgGGZMh0LQG6CZsvAFS4r2Y4ItUuCQc8TQ3urB30mQ==
+ dependencies:
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ postcss "^8.4.38"
+ postcss-scss "^4.0.9"
+
+svelte-hmr@^0.16.0:
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.16.0.tgz#9f345b7d1c1662f1613747ed7e82507e376c1716"
+ integrity sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==
+
+svelte-preprocess@^5.1.3:
+ version "5.1.4"
+ resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz#14ada075c94bbd2b71c5ec70ff72f8ebe1c95b91"
+ integrity sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==
+ dependencies:
+ "@types/pug" "^2.0.6"
+ detect-indent "^6.1.0"
+ magic-string "^0.30.5"
+ sorcery "^0.11.0"
+ strip-indent "^3.0.0"
+
+svelte@^4.2.7:
+ version "4.2.18"
+ resolved "https://registry.yarnpkg.com/svelte/-/svelte-4.2.18.tgz#33dbce74e83eb6dcc54dbea25f9758b1d8e8bb78"
+ integrity sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==
+ dependencies:
+ "@ampproject/remapping" "^2.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.15"
+ "@jridgewell/trace-mapping" "^0.3.18"
+ "@types/estree" "^1.0.1"
+ acorn "^8.9.0"
+ aria-query "^5.3.0"
+ axobject-query "^4.0.0"
+ code-red "^1.0.3"
+ css-tree "^2.3.1"
+ estree-walker "^3.0.3"
+ is-reference "^3.0.1"
+ locate-character "^3.0.0"
+ magic-string "^0.30.4"
+ periscopic "^3.1.0"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+tiny-glob@^0.2.9:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
+ integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==
+ dependencies:
+ globalyzer "0.1.0"
+ globrex "^0.1.2"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
+ts-api-utils@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
+ integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
+
+tslib@^2.4.1:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
+ integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+typescript-eslint@^8.0.0-alpha.20:
+ version "8.0.0-alpha.29"
+ resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.0.0-alpha.29.tgz#939c996f681e3ae6bb36c1159b1af16e4a84f2fd"
+ integrity sha512-NASQjd4tP+wukSs/Cj8vHjK/Ogk0nhVOr/kwzwg0AaXOWiz0g+rtE+lvqAaV+nhsCfMskuzKzc1TywFrhJlbvw==
+ dependencies:
+ "@typescript-eslint/eslint-plugin" "8.0.0-alpha.29"
+ "@typescript-eslint/parser" "8.0.0-alpha.29"
+ "@typescript-eslint/utils" "8.0.0-alpha.29"
+
+typescript@^5.0.0, typescript@^5.0.3:
+ version "5.4.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
+ integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+util-deprecate@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+vite@^5.0.3:
+ version "5.2.13"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.13.tgz#945ababcbe3d837ae2479c29f661cd20bc5e1a80"
+ integrity sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==
+ dependencies:
+ esbuild "^0.20.1"
+ postcss "^8.4.38"
+ rollup "^4.13.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+vitefu@^0.2.5:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.5.tgz#c1b93c377fbdd3e5ddd69840ea3aa70b40d90969"
+ integrity sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+yaml@^1.10.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
From a2f001392b315b53d210909f45caa1a559722ecc Mon Sep 17 00:00:00 2001
From: sam
Date: Sun, 9 Jun 2024 15:48:26 +0200
Subject: [PATCH 004/261] stuff
---
.editorconfig | 3 +
.../Controllers/MetaController.cs | 9 ++-
Foxnouns.Frontend/src/app.html | 7 ++
Foxnouns.Frontend/src/lib/api/meta.ts | 11 +++
Foxnouns.Frontend/src/lib/api/user.ts | 9 +++
Foxnouns.Frontend/src/lib/nav/Logo.svelte | 34 +++++++++
Foxnouns.Frontend/src/lib/nav/Navbar.svelte | 57 +++++++++++++++
Foxnouns.Frontend/src/lib/request.ts | 72 +++++++++++++++++++
.../src/routes/+layout.server.ts | 13 ++++
Foxnouns.Frontend/src/routes/+layout.svelte | 11 +++
Foxnouns.Frontend/src/routes/+page.svelte | 17 +++++
11 files changed, 241 insertions(+), 2 deletions(-)
create mode 100644 Foxnouns.Frontend/src/lib/api/meta.ts
create mode 100644 Foxnouns.Frontend/src/lib/api/user.ts
create mode 100644 Foxnouns.Frontend/src/lib/nav/Logo.svelte
create mode 100644 Foxnouns.Frontend/src/lib/nav/Navbar.svelte
create mode 100644 Foxnouns.Frontend/src/lib/request.ts
create mode 100644 Foxnouns.Frontend/src/routes/+layout.server.ts
create mode 100644 Foxnouns.Frontend/src/routes/+layout.svelte
diff --git a/.editorconfig b/.editorconfig
index 2a1f655..0229143 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,2 +1,5 @@
[*.cs]
+# We use PostgresSQL which doesn't recommend more specific string types
resharper_entity_framework_model_validation_unlimited_string_length_highlighting = none
+# This is raised for every single property of records returned by endpoints
+resharper_not_accessed_positional_property_local_highlighting = none
diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs
index d43749e..451960e 100644
--- a/Foxnouns.Backend/Controllers/MetaController.cs
+++ b/Foxnouns.Backend/Controllers/MetaController.cs
@@ -14,8 +14,13 @@ public class MetaController(DatabaseContext db) : ApiControllerBase
var userCount = await db.Users.CountAsync();
var memberCount = await db.Members.CountAsync();
- return Ok(new MetaResponse(userCount, memberCount, BuildInfo.Version, BuildInfo.Hash));
+ return Ok(new MetaResponse(
+ BuildInfo.Version, BuildInfo.Hash, memberCount,
+ new UserInfo(userCount, 0, 0, 0))
+ );
}
- private record MetaResponse(int Users, int Members, string Version, string Hash);
+ private record MetaResponse(string Version, string Hash, int Members, UserInfo Users);
+
+ private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay);
}
\ No newline at end of file
diff --git a/Foxnouns.Frontend/src/app.html b/Foxnouns.Frontend/src/app.html
index 77a5ff5..562d998 100644
--- a/Foxnouns.Frontend/src/app.html
+++ b/Foxnouns.Frontend/src/app.html
@@ -4,6 +4,13 @@
+
%sveltekit.head%
diff --git a/Foxnouns.Frontend/src/lib/api/meta.ts b/Foxnouns.Frontend/src/lib/api/meta.ts
new file mode 100644
index 0000000..89f1aa3
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/api/meta.ts
@@ -0,0 +1,11 @@
+export default interface Meta {
+ version: string;
+ hash: string;
+ users: {
+ total: number;
+ active_month: number;
+ active_week: number;
+ active_day: number;
+ };
+ members: number;
+}
diff --git a/Foxnouns.Frontend/src/lib/api/user.ts b/Foxnouns.Frontend/src/lib/api/user.ts
new file mode 100644
index 0000000..3832872
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/api/user.ts
@@ -0,0 +1,9 @@
+export type User = {
+ id: string;
+ username: string;
+ display_name: string | null;
+ bio: string | null;
+ member_title: string | null;
+ avatar_url: string | null;
+ links: string[];
+};
diff --git a/Foxnouns.Frontend/src/lib/nav/Logo.svelte b/Foxnouns.Frontend/src/lib/nav/Logo.svelte
new file mode 100644
index 0000000..9da9d99
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/nav/Logo.svelte
@@ -0,0 +1,34 @@
+
diff --git a/Foxnouns.Frontend/src/lib/nav/Navbar.svelte b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte
new file mode 100644
index 0000000..588bb44
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Foxnouns.Frontend/src/lib/request.ts b/Foxnouns.Frontend/src/lib/request.ts
new file mode 100644
index 0000000..9536ca9
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/request.ts
@@ -0,0 +1,72 @@
+import { PUBLIC_API_BASE } from "$env/static/public";
+
+export type RequestParams = {
+ token?: string;
+ body?: any;
+ headers?: Record;
+};
+
+/**
+ * Fetch a path from the API and parse the response.
+ * To make sure the request is authenticated in load functions,
+ * pass `fetch` from the request object into opts.
+ *
+ * @param fetchFn A function like `fetch`, such as from the `load` function
+ * @param method The HTTP method, i.e. GET, POST, PATCH
+ * @param path The path to request, minus the leading `/api/v2`
+ * @param params Extra options for this request
+ * @returns T
+ * @throws APIError
+ */
+export default async function request(
+ fetchFn: typeof fetch,
+ method: string,
+ path: string,
+ params: RequestParams = {},
+) {
+ const url = `${PUBLIC_API_BASE}/v2${path}`;
+ const resp = await fetchFn(url, {
+ method,
+ body: params.body ? JSON.stringify(params.body) : undefined,
+ headers: {
+ ...params.headers,
+ ...(params.token ? { Authorization: params.token } : {}),
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (resp.status < 200 || resp.status >= 400) throw await resp.json();
+ return (await resp.json()) as T;
+}
+
+/**
+ * Fetch a path from the API and discard the response.
+ * To make sure the request is authenticated in load functions,
+ * pass `fetch` from the request object into opts.
+ *
+ * @param fetchFn A function like `fetch`, such as from the `load` function
+ * @param method The HTTP method, i.e. GET, POST, PATCH
+ * @param path The path to request, minus the leading `/api/v2`
+ * @param params Extra options for this request
+ * @returns T
+ * @throws APIError
+ */
+export async function fastRequest(
+ fetchFn: typeof fetch,
+ method: string,
+ path: string,
+ params: RequestParams = {},
+): Promise {
+ const url = `${PUBLIC_API_BASE}/v2${path}`;
+ const resp = await fetchFn(url, {
+ method,
+ body: params.body ? JSON.stringify(params.body) : undefined,
+ headers: {
+ ...params.headers,
+ ...(params.token ? { Authorization: params.token } : {}),
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (resp.status < 200 || resp.status >= 400) throw await resp.json();
+}
diff --git a/Foxnouns.Frontend/src/routes/+layout.server.ts b/Foxnouns.Frontend/src/routes/+layout.server.ts
new file mode 100644
index 0000000..2d1e4ba
--- /dev/null
+++ b/Foxnouns.Frontend/src/routes/+layout.server.ts
@@ -0,0 +1,13 @@
+import type Meta from "$lib/api/meta";
+import type { User } from "$lib/api/user";
+import request from "$lib/request";
+
+export async function load({ fetch }) {
+ const meta = await request(fetch, "GET", "/meta");
+ let user: User | undefined;
+ try {
+ user = await request(fetch, "GET", "/users/@me");
+ } catch {}
+
+ return { meta, user };
+}
diff --git a/Foxnouns.Frontend/src/routes/+layout.svelte b/Foxnouns.Frontend/src/routes/+layout.svelte
new file mode 100644
index 0000000..64a204f
--- /dev/null
+++ b/Foxnouns.Frontend/src/routes/+layout.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Foxnouns.Frontend/src/routes/+page.svelte b/Foxnouns.Frontend/src/routes/+page.svelte
index 5982b0a..0f5264b 100644
--- a/Foxnouns.Frontend/src/routes/+page.svelte
+++ b/Foxnouns.Frontend/src/routes/+page.svelte
@@ -1,2 +1,19 @@
+
+
Welcome to SvelteKit
Visit kit.svelte.dev to read the documentation
+
+
+ are you logged in? {data.user !== undefined}
+ {#if data.user}
+
hello, {data.user.username}!
+
your ID: {data.user.id}
+ {/if}
+
+
+
+ stats: {data.meta.users.total} users, {data.meta.members} members
+
From 50257d61f8c6596c7640df4430e7bf7feb1f71f5 Mon Sep 17 00:00:00 2001
From: sam
Date: Sun, 9 Jun 2024 23:21:28 +0200
Subject: [PATCH 005/261] switch frontend css from bootstrap to bulma
---
.../Controllers/UsersController.cs | 1 -
Foxnouns.Backend/Database/Models/Cache.cs | 11 ++
Foxnouns.Frontend/package.json | 6 +-
Foxnouns.Frontend/src/app.html | 4 +-
Foxnouns.Frontend/src/app.scss | 7 ++
Foxnouns.Frontend/src/lib/nav/Dropdown.svelte | 10 ++
.../src/lib/nav/DropdownItem.svelte | 10 ++
Foxnouns.Frontend/src/lib/nav/Navbar.svelte | 103 ++++++++++--------
Foxnouns.Frontend/src/lib/store.ts | 9 ++
Foxnouns.Frontend/src/routes/+layout.svelte | 5 +-
Foxnouns.Frontend/src/routes/+page.svelte | 2 +-
.../src/routes/menu/+page.server.ts | 0
.../src/routes/menu/+page.svelte | 0
Foxnouns.Frontend/yarn.lock | 53 ++++++---
14 files changed, 150 insertions(+), 71 deletions(-)
create mode 100644 Foxnouns.Backend/Database/Models/Cache.cs
create mode 100644 Foxnouns.Frontend/src/app.scss
create mode 100644 Foxnouns.Frontend/src/lib/nav/Dropdown.svelte
create mode 100644 Foxnouns.Frontend/src/lib/nav/DropdownItem.svelte
create mode 100644 Foxnouns.Frontend/src/lib/store.ts
create mode 100644 Foxnouns.Frontend/src/routes/menu/+page.server.ts
create mode 100644 Foxnouns.Frontend/src/routes/menu/+page.svelte
diff --git a/Foxnouns.Backend/Controllers/UsersController.cs b/Foxnouns.Backend/Controllers/UsersController.cs
index 26ae497..c6101bb 100644
--- a/Foxnouns.Backend/Controllers/UsersController.cs
+++ b/Foxnouns.Backend/Controllers/UsersController.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
diff --git a/Foxnouns.Backend/Database/Models/Cache.cs b/Foxnouns.Backend/Database/Models/Cache.cs
new file mode 100644
index 0000000..81d4b2b
--- /dev/null
+++ b/Foxnouns.Backend/Database/Models/Cache.cs
@@ -0,0 +1,11 @@
+using NodaTime;
+
+namespace Foxnouns.Backend.Database.Models;
+
+public class Cache
+{
+ public long Id { get; init; }
+ public required string Key { get; init; }
+ public required string Value { get; set; }
+ public Instant Expires { get; init; }
+}
\ No newline at end of file
diff --git a/Foxnouns.Frontend/package.json b/Foxnouns.Frontend/package.json
index fb3ce1f..5b9442e 100644
--- a/Foxnouns.Frontend/package.json
+++ b/Foxnouns.Frontend/package.json
@@ -12,18 +12,20 @@
"format": "prettier --write ."
},
"devDependencies": {
+ "@fontsource/firago": "^5.0.11",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
- "@sveltestrap/sveltestrap": "^6.2.7",
+ "@tabler/icons-svelte": "^3.5.0",
"@types/eslint": "^8.56.7",
- "bootstrap": "^5.3.3",
+ "bulma": "^1.0.1",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.0.0",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
+ "sass": "^1.77.4",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
diff --git a/Foxnouns.Frontend/src/app.html b/Foxnouns.Frontend/src/app.html
index 562d998..1bf633d 100644
--- a/Foxnouns.Frontend/src/app.html
+++ b/Foxnouns.Frontend/src/app.html
@@ -6,10 +6,10 @@
%sveltekit.head%
diff --git a/Foxnouns.Frontend/src/app.scss b/Foxnouns.Frontend/src/app.scss
new file mode 100644
index 0000000..e8dcaf9
--- /dev/null
+++ b/Foxnouns.Frontend/src/app.scss
@@ -0,0 +1,7 @@
+@use "bulma/sass" with (
+ $family-primary: "FiraGO"
+);
+
+@import "@fontsource/firago/400.css";
+@import "@fontsource/firago/400-italic.css";
+@import "@fontsource/firago/700.css";
diff --git a/Foxnouns.Frontend/src/lib/nav/Dropdown.svelte b/Foxnouns.Frontend/src/lib/nav/Dropdown.svelte
new file mode 100644
index 0000000..00c70c5
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/nav/Dropdown.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Foxnouns.Frontend/src/lib/nav/DropdownItem.svelte b/Foxnouns.Frontend/src/lib/nav/DropdownItem.svelte
new file mode 100644
index 0000000..be22533
--- /dev/null
+++ b/Foxnouns.Frontend/src/lib/nav/DropdownItem.svelte
@@ -0,0 +1,10 @@
+
+
+{#if divider}
+
+{:else}
+
+{/if}
diff --git a/Foxnouns.Frontend/src/lib/nav/Navbar.svelte b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte
index 588bb44..7effb19 100644
--- a/Foxnouns.Frontend/src/lib/nav/Navbar.svelte
+++ b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte
@@ -1,57 +1,70 @@
-
-
-
-
-
-
-
- stats: {data.meta.users.total} users, {data.meta.members} members
+ stats: {data.meta.users.total} users, {data.meta.members} members
diff --git a/Foxnouns.Frontend/src/routes/menu/+page.server.ts b/Foxnouns.Frontend/src/routes/menu/+page.server.ts
new file mode 100644
index 0000000..e69de29
diff --git a/Foxnouns.Frontend/src/routes/menu/+page.svelte b/Foxnouns.Frontend/src/routes/menu/+page.svelte
new file mode 100644
index 0000000..e69de29
diff --git a/Foxnouns.Frontend/yarn.lock b/Foxnouns.Frontend/yarn.lock
index 7eccad0..93fffb6 100644
--- a/Foxnouns.Frontend/yarn.lock
+++ b/Foxnouns.Frontend/yarn.lock
@@ -171,6 +171,11 @@
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.3.tgz#e65ae80ee2927b4fd8c5c26b15ecacc2b2a6cc2a"
integrity sha512-HAbhAYKfsAC2EkTqve00ibWIZlaU74Z1EHwAjYr4PXF0YU2VEA1zSIKSSpKszRLRWwHzzRZXvK632u+uXzvsvw==
+"@fontsource/firago@^5.0.11":
+ version "5.0.11"
+ resolved "https://registry.yarnpkg.com/@fontsource/firago/-/firago-5.0.11.tgz#8fe3c8b47cc1d8148bc50c80189ed3aac8555cb7"
+ integrity sha512-XfFsLxSFMTbJTN+94yFTJyuFGmoxtykt+6rL0fj9unCeXslllirpH6KetIlbZO73NzTUmKYRvtOJdOgVbBGtaQ==
+
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
@@ -239,11 +244,6 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817"
integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==
-"@popperjs/core@^2.11.8":
- version "2.11.8"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
- integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
-
"@rollup/plugin-commonjs@^25.0.7":
version "25.0.8"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz#c77e608ab112a666b7f2a6bea625c73224f7dd34"
@@ -412,12 +412,17 @@
svelte-hmr "^0.16.0"
vitefu "^0.2.5"
-"@sveltestrap/sveltestrap@^6.2.7":
- version "6.2.7"
- resolved "https://registry.yarnpkg.com/@sveltestrap/sveltestrap/-/sveltestrap-6.2.7.tgz#5b2736cbee2db973f02b09d2e9d5bf819418f1f9"
- integrity sha512-WwLLfAFUb42BGuRrf3Vbct30bQMzlEMMipN/MfxhjuLTmLQeW9muVJfPyvjtWS+mY+RjkSCoHvAp/ZobP1NLlQ==
+"@tabler/icons-svelte@^3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@tabler/icons-svelte/-/icons-svelte-3.5.0.tgz#02efede4ce0ed680e0835878c6c02cd63daf9d9a"
+ integrity sha512-mc5ardGEM7cnUA4/q6Mz5bmW9B6t28vAAOf4Wl6+KXiTwG00EjImfnIr3pS3Ihi9sFIiXvJPYRl4H5IHlgvJvQ==
dependencies:
- "@popperjs/core" "^2.11.8"
+ "@tabler/icons" "3.5.0"
+
+"@tabler/icons@3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.5.0.tgz#29d0dbf100c8cb392dd64f1fe8efdcfcd1f57e44"
+ integrity sha512-I53dC3ZSHQ2MZFGvDYJelfXm91L2bTTixS4w5jTAulLhHbCZso5Bih4Rk/NYZxlngLQMKHvEYwZQ+6w/WluKiA==
"@types/cookie@^0.6.0":
version "0.6.0"
@@ -607,11 +612,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
-bootstrap@^5.3.3:
- version "5.3.3"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38"
- integrity sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==
-
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -644,6 +644,11 @@ builtin-modules@^3.3.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
+bulma@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bulma/-/bulma-1.0.1.tgz#e37261d6f8e1a3494c9378803d9958effb2715ce"
+ integrity sha512-+xv/BIAEQakHkR0QVz+s+RjNqfC53Mx9ZYexyaFNFo9wx5i76HXArNdwW7bccyJxa5mgV/T5DcVGqsAB19nBJQ==
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -657,7 +662,7 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chokidar@^3.4.1:
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -1144,6 +1149,11 @@ ignore@^5.2.0, ignore@^5.3.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
+immutable@^4.0.0:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447"
+ integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==
+
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -1630,6 +1640,15 @@ sander@^0.5.0:
mkdirp "^0.5.1"
rimraf "^2.5.2"
+sass@^1.77.4:
+ version "1.77.4"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.4.tgz#92059c7bfc56b827c56eb116778d157ec017a5cd"
+ integrity sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
semver@^7.5.4, semver@^7.6.0:
version "7.6.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
@@ -1676,7 +1695,7 @@ sorcery@^0.11.0:
minimist "^1.2.0"
sander "^0.5.0"
-source-map-js@^1.0.1, source-map-js@^1.2.0:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
From 493a6e4d298cd767ffa48789891d97cb4f80323c Mon Sep 17 00:00:00 2001
From: sam
Date: Mon, 10 Jun 2024 16:25:49 +0200
Subject: [PATCH 006/261] feat(backend): add skeleton discord auth controller
---
.../Authentication/DiscordAuthController.cs | 14 +++++++++
Foxnouns.Backend/Database/DatabaseContext.cs | 2 ++
.../Database/DatabaseQueryExtensions.cs | 31 +++++++++++++++++++
.../Models/{Cache.cs => TemporaryKey.cs} | 2 +-
4 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
rename Foxnouns.Backend/Database/Models/{Cache.cs => TemporaryKey.cs} (89%)
diff --git a/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs b/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
new file mode 100644
index 0000000..4934ff5
--- /dev/null
+++ b/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
@@ -0,0 +1,14 @@
+using Foxnouns.Backend.Database;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Foxnouns.Backend.Controllers.Authentication;
+
+[Route("/api/v2/auth/discord")]
+public class DiscordAuthController(Config config, DatabaseContext db) : ApiControllerBase
+{
+ [HttpPost("url")]
+ public async Task AuthenticationUrl()
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs
index f9ec686..03e2c42 100644
--- a/Foxnouns.Backend/Database/DatabaseContext.cs
+++ b/Foxnouns.Backend/Database/DatabaseContext.cs
@@ -17,6 +17,7 @@ public class DatabaseContext : DbContext
public DbSet FediverseApplications { get; set; }
public DbSet Tokens { get; set; }
public DbSet Applications { get; set; }
+ public DbSet TemporaryKeys { get; set; }
public DatabaseContext(Config config)
{
@@ -47,6 +48,7 @@ public class DatabaseContext : DbContext
{
modelBuilder.Entity().HasIndex(u => u.Username).IsUnique();
modelBuilder.Entity().HasIndex(m => new { m.UserId, m.Name }).IsUnique();
+ modelBuilder.Entity().HasIndex(k => k.Key).IsUnique();
modelBuilder.Entity()
.OwnsOne(u => u.Fields, f => f.ToJson())
diff --git a/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs b/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs
index 40e333c..0b77ddb 100644
--- a/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs
+++ b/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs
@@ -1,7 +1,9 @@
using System.Security.Cryptography;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Utils;
+using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.EntityFrameworkCore;
+using NodaTime;
namespace Foxnouns.Backend.Database;
@@ -84,4 +86,33 @@ public static class DatabaseQueryExtensions
await context.SaveChangesAsync();
return app;
}
+
+ public static Task SetKeyAsync(this DatabaseContext context, string key, string value, Duration expireAfter) =>
+ context.SetKeyAsync(key, value, SystemClock.Instance.GetCurrentInstant() + expireAfter);
+
+ public static async Task SetKeyAsync(this DatabaseContext context, string key, string value, Instant expires)
+ {
+ context.TemporaryKeys.Add(new TemporaryKey
+ {
+ Expires = expires,
+ Key = key,
+ Value = value,
+ });
+ await context.SaveChangesAsync();
+ }
+
+ public static async Task GetKeyAsync(this DatabaseContext context, string key,
+ bool delete = false)
+ {
+ var value = await context.TemporaryKeys.FirstOrDefaultAsync(k => k.Key == key);
+ if (value == null) return null;
+
+ if (delete)
+ {
+ await context.TemporaryKeys.Where(k => k.Key == key).ExecuteDeleteAsync();
+ await context.SaveChangesAsync();
+ }
+
+ return value.Value;
+ }
}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Database/Models/Cache.cs b/Foxnouns.Backend/Database/Models/TemporaryKey.cs
similarity index 89%
rename from Foxnouns.Backend/Database/Models/Cache.cs
rename to Foxnouns.Backend/Database/Models/TemporaryKey.cs
index 81d4b2b..d3dbfc8 100644
--- a/Foxnouns.Backend/Database/Models/Cache.cs
+++ b/Foxnouns.Backend/Database/Models/TemporaryKey.cs
@@ -2,7 +2,7 @@ using NodaTime;
namespace Foxnouns.Backend.Database.Models;
-public class Cache
+public class TemporaryKey
{
public long Id { get; init; }
public required string Key { get; init; }
From 25540f2de2184a4b13e054624f4c1e552f849c95 Mon Sep 17 00:00:00 2001
From: sam
Date: Wed, 12 Jun 2024 03:47:20 +0200
Subject: [PATCH 007/261] feat(backend): start authentication controllers
---
.gitignore | 1 +
Foxnouns.Backend/Config.cs | 25 +-
.../Authentication/AuthController.cs | 38 ++
.../Authentication/DiscordAuthController.cs | 8 +-
.../Authentication/EmailAuthController.cs | 31 ++
.../Controllers/DebugController.cs | 2 +-
...611225328_AddTemporaryKeyCache.Designer.cs | 511 ++++++++++++++++++
.../20240611225328_AddTemporaryKeyCache.cs | 44 ++
.../DatabaseContextModelSnapshot.cs | 33 ++
.../Extensions/WebApplicationExtensions.cs | 3 +-
Foxnouns.Backend/Program.cs | 23 +-
Foxnouns.Backend/Services/AuthService.cs | 18 +
Foxnouns.Backend/Services/KeyCacheService.cs | 51 ++
.../Services/UserRendererService.cs | 2 +-
.../{config.ini => config.example.ini} | 4 +
15 files changed, 777 insertions(+), 17 deletions(-)
create mode 100644 Foxnouns.Backend/Controllers/Authentication/AuthController.cs
create mode 100644 Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs
create mode 100644 Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.Designer.cs
create mode 100644 Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.cs
create mode 100644 Foxnouns.Backend/Services/KeyCacheService.cs
rename Foxnouns.Backend/{config.ini => config.example.ini} (88%)
diff --git a/.gitignore b/.gitignore
index cd1b080..56d5d08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
bin/
obj/
.version
+config.ini
diff --git a/Foxnouns.Backend/Config.cs b/Foxnouns.Backend/Config.cs
index 39a417f..bb2add8 100644
--- a/Foxnouns.Backend/Config.cs
+++ b/Foxnouns.Backend/Config.cs
@@ -4,21 +4,28 @@ namespace Foxnouns.Backend;
public class Config
{
- public string Host { get; set; } = "localhost";
- public int Port { get; set; } = 3000;
- public string BaseUrl { get; set; } = null!;
+ public string Host { get; init; } = "localhost";
+ public int Port { get; init; } = 3000;
+ public string BaseUrl { get; init; } = null!;
public string Address => $"http://{Host}:{Port}";
- public string? SeqLogUrl { get; set; }
- public LogEventLevel LogEventLevel { get; set; } = LogEventLevel.Debug;
+ public string? SeqLogUrl { get; init; }
+ public LogEventLevel LogEventLevel { get; init; } = LogEventLevel.Debug;
- public DatabaseConfig Database { get; set; } = new();
+ public DatabaseConfig Database { get; init; } = new();
+ public DiscordAuthConfig DiscordAuth { get; init; } = new();
public class DatabaseConfig
{
- public string Url { get; set; } = string.Empty;
- public int? Timeout { get; set; }
- public int? MaxPoolSize { get; set; }
+ public string Url { get; init; } = string.Empty;
+ public int? Timeout { get; init; }
+ public int? MaxPoolSize { get; init; }
+ }
+
+ public class DiscordAuthConfig
+ {
+ public string? ClientId { get; init; }
+ public string? ClientSecret { get; init; }
}
}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/Authentication/AuthController.cs b/Foxnouns.Backend/Controllers/Authentication/AuthController.cs
new file mode 100644
index 0000000..96b10c3
--- /dev/null
+++ b/Foxnouns.Backend/Controllers/Authentication/AuthController.cs
@@ -0,0 +1,38 @@
+using System.Web;
+using Foxnouns.Backend.Services;
+using Microsoft.AspNetCore.Mvc;
+using NodaTime;
+
+namespace Foxnouns.Backend.Controllers.Authentication;
+
+[Route("/api/v2/auth")]
+public class AuthController(Config config, KeyCacheService keyCacheSvc) : ApiControllerBase
+{
+ [HttpPost("urls")]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UrlsResponse))]
+ public async Task UrlsAsync()
+ {
+ var state = HttpUtility.UrlEncode(await keyCacheSvc.GenerateAuthStateAsync());
+ string? discord = null;
+ if (config.DiscordAuth.ClientId != null && config.DiscordAuth.ClientSecret != null)
+ discord =
+ $"https://discord.com/oauth2/authorize?response_type=code" +
+ $"&client_id={config.DiscordAuth.ClientId}&scope=identify" +
+ $"&prompt=none&state={state}" +
+ $"&redirect_uri={HttpUtility.UrlEncode($"{config.BaseUrl}/auth/login/discord")}";
+
+ return Ok(new UrlsResponse(discord, null, null));
+ }
+
+ private record UrlsResponse(
+ string? Discord,
+ string? Google,
+ string? Tumblr
+ );
+
+ internal record AuthResponse(
+ UserRendererService.UserResponse User,
+ string Token,
+ Instant ExpiresAt
+ );
+}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs b/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
index 4934ff5..2fb8c54 100644
--- a/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
+++ b/Foxnouns.Backend/Controllers/Authentication/DiscordAuthController.cs
@@ -6,9 +6,11 @@ namespace Foxnouns.Backend.Controllers.Authentication;
[Route("/api/v2/auth/discord")]
public class DiscordAuthController(Config config, DatabaseContext db) : ApiControllerBase
{
- [HttpPost("url")]
- public async Task AuthenticationUrl()
+ private void CheckRequirements()
{
- throw new NotImplementedException();
+ if (config.DiscordAuth.ClientId == null || config.DiscordAuth.ClientSecret == null)
+ {
+ throw new ApiError.BadRequest("Discord authentication is not enabled on this instance.");
+ }
}
}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs b/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs
new file mode 100644
index 0000000..3ba92ba
--- /dev/null
+++ b/Foxnouns.Backend/Controllers/Authentication/EmailAuthController.cs
@@ -0,0 +1,31 @@
+using Foxnouns.Backend.Database;
+using Foxnouns.Backend.Services;
+using Microsoft.AspNetCore.Mvc;
+using NodaTime;
+
+namespace Foxnouns.Backend.Controllers.Authentication;
+
+[Route("/api/v2/auth/email")]
+public class EmailAuthController(DatabaseContext db, AuthService authSvc, UserRendererService userRendererSvc, IClock clock, ILogger logger) : ApiControllerBase
+{
+ [HttpPost("login")]
+ public async Task LoginAsync([FromBody] LoginRequest req)
+ {
+ var user = await authSvc.AuthenticateUserAsync(req.Email, req.Password);
+ var frontendApp = await db.GetFrontendApplicationAsync();
+
+ var (tokenStr, token) =
+ authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365));
+ db.Add(token);
+
+ await db.SaveChangesAsync();
+
+ return Ok(new AuthController.AuthResponse(
+ await userRendererSvc.RenderUserAsync(user, selfUser: user, renderMembers: false),
+ tokenStr,
+ token.ExpiresAt
+ ));
+ }
+
+ public record LoginRequest(string Email, string Password);
+}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Controllers/DebugController.cs b/Foxnouns.Backend/Controllers/DebugController.cs
index 4746d95..3ef189b 100644
--- a/Foxnouns.Backend/Controllers/DebugController.cs
+++ b/Foxnouns.Backend/Controllers/DebugController.cs
@@ -10,7 +10,7 @@ public class DebugController(DatabaseContext db, AuthService authSvc, IClock clo
{
[HttpPost("users")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthResponse))]
- public async Task CreateUser([FromBody] CreateUserRequest req)
+ public async Task CreateUserAsync([FromBody] CreateUserRequest req)
{
logger.Debug("Creating user with username {Username} and email {Email}", req.Username, req.Email);
diff --git a/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.Designer.cs b/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.Designer.cs
new file mode 100644
index 0000000..af4f52a
--- /dev/null
+++ b/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.Designer.cs
@@ -0,0 +1,511 @@
+//
+using Foxnouns.Backend.Database;
+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("20240611225328_AddTemporaryKeyCache")]
+ partial class AddTemporaryKeyCache
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ 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.Property("RedirectUris")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("redirect_uris");
+
+ b.Property("Scopes")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("scopes");
+
+ b.HasKey("Id")
+ .HasName("pk_applications");
+
+ b.ToTable("applications", (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.ToTable("auth_methods", (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("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("Links")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("links");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.Property("Unlisted")
+ .HasColumnType("boolean")
+ .HasColumnName("unlisted");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("pk_members");
+
+ b.HasIndex("UserId", "Name")
+ .IsUnique()
+ .HasDatabaseName("ix_members_user_id_name");
+
+ b.ToTable("members", (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")
+ .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.Property("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("DisplayName")
+ .HasColumnType("text")
+ .HasColumnName("display_name");
+
+ b.Property("Links")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("links");
+
+ b.Property("ListHidden")
+ .HasColumnType("boolean")
+ .HasColumnName("list_hidden");
+
+ b.Property("MemberTitle")
+ .HasColumnType("text")
+ .HasColumnName("member_title");
+
+ b.Property("Password")
+ .HasColumnType("text")
+ .HasColumnName("password");
+
+ b.Property("Role")
+ .HasColumnType("integer")
+ .HasColumnName("role");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("username");
+
+ b.HasKey("Id")
+ .HasName("pk_users");
+
+ b.HasIndex("Username")
+ .IsUnique()
+ .HasDatabaseName("ix_users_username");
+
+ b.ToTable("users", (string)null);
+ });
+
+ 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.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.OwnsOne("System.Collections.Generic.List", "Fields", b1 =>
+ {
+ b1.Property("MemberId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("MemberId");
+
+ b1.ToTable("members");
+
+ b1.ToJson("fields");
+
+ b1.WithOwner()
+ .HasForeignKey("MemberId")
+ .HasConstraintName("fk_members_members_id");
+ });
+
+ b.OwnsOne("System.Collections.Generic.List", "Names", b1 =>
+ {
+ b1.Property("MemberId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("MemberId");
+
+ b1.ToTable("members");
+
+ b1.ToJson("names");
+
+ b1.WithOwner()
+ .HasForeignKey("MemberId")
+ .HasConstraintName("fk_members_members_id");
+ });
+
+ b.OwnsOne("System.Collections.Generic.List", "Pronouns", b1 =>
+ {
+ b1.Property("MemberId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("MemberId");
+
+ b1.ToTable("members");
+
+ b1.ToJson("pronouns");
+
+ b1.WithOwner()
+ .HasForeignKey("MemberId")
+ .HasConstraintName("fk_members_members_id");
+ });
+
+ b.Navigation("Fields")
+ .IsRequired();
+
+ b.Navigation("Names")
+ .IsRequired();
+
+ b.Navigation("Pronouns")
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ 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.User", b =>
+ {
+ b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 =>
+ {
+ b1.Property("UserId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("UserId")
+ .HasName("pk_users");
+
+ b1.ToTable("users");
+
+ b1.ToJson("fields");
+
+ b1.WithOwner()
+ .HasForeignKey("UserId")
+ .HasConstraintName("fk_users_users_user_id");
+ });
+
+ b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 =>
+ {
+ b1.Property("UserId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("UserId")
+ .HasName("pk_users");
+
+ b1.ToTable("users");
+
+ b1.ToJson("names");
+
+ b1.WithOwner()
+ .HasForeignKey("UserId")
+ .HasConstraintName("fk_users_users_user_id");
+ });
+
+ b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 =>
+ {
+ b1.Property("UserId")
+ .HasColumnType("bigint");
+
+ b1.Property("Capacity")
+ .HasColumnType("integer");
+
+ b1.HasKey("UserId")
+ .HasName("pk_users");
+
+ b1.ToTable("users");
+
+ b1.ToJson("pronouns");
+
+ b1.WithOwner()
+ .HasForeignKey("UserId")
+ .HasConstraintName("fk_users_users_user_id");
+ });
+
+ b.Navigation("Fields")
+ .IsRequired();
+
+ b.Navigation("Names")
+ .IsRequired();
+
+ b.Navigation("Pronouns")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b =>
+ {
+ b.Navigation("AuthMethods");
+
+ b.Navigation("Members");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.cs b/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.cs
new file mode 100644
index 0000000..ccf736b
--- /dev/null
+++ b/Foxnouns.Backend/Database/Migrations/20240611225328_AddTemporaryKeyCache.cs
@@ -0,0 +1,44 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Foxnouns.Backend.Database.Migrations
+{
+ ///
+ public partial class AddTemporaryKeyCache : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "temporary_keys",
+ columns: table => new
+ {
+ id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ key = table.Column(type: "text", nullable: false),
+ value = table.Column(type: "text", nullable: false),
+ expires = table.Column(type: "timestamp with time zone", 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);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "temporary_keys");
+ }
+ }
+}
diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs
index d0cb607..13c10dd 100644
--- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs
+++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs
@@ -175,6 +175,39 @@ namespace Foxnouns.Backend.Database.Migrations
b.ToTable("members", (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")
diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs
index 84381c4..158eb10 100644
--- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs
+++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs
@@ -66,7 +66,8 @@ public static class WebApplicationExtensions
.AddSnowflakeGenerator()
.AddScoped()
.AddScoped()
- .AddScoped();
+ .AddScoped()
+ .AddScoped();
public static IServiceCollection AddCustomMiddleware(this IServiceCollection services) => services
.AddScoped()
diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs
index 5025f70..b3c623b 100644
--- a/Foxnouns.Backend/Program.cs
+++ b/Foxnouns.Backend/Program.cs
@@ -2,9 +2,11 @@ using Foxnouns.Backend;
using Foxnouns.Backend.Database;
using Serilog;
using Foxnouns.Backend.Extensions;
+using Foxnouns.Backend.Services;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
+using NodaTime;
// Read version information from .version in the repository root
await BuildInfo.ReadBuildInfo();
@@ -60,6 +62,23 @@ app.MapControllers();
app.Urls.Clear();
app.Urls.Add(config.Address);
-app.Run();
+// Fire off the periodic tasks loop in the background
+_ = new Timer(_ =>
+{
+ var __ = RunPeriodicTasksAsync();
+}, null, TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1));
-Log.CloseAndFlush();
\ No newline at end of file
+app.Run();
+Log.CloseAndFlush();
+
+return;
+
+async Task RunPeriodicTasksAsync()
+{
+ await using var scope = app.Services.CreateAsyncScope();
+ var logger = scope.ServiceProvider.GetRequiredService();
+ logger.Debug("Running periodic tasks");
+
+ var keyCacheSvc = scope.ServiceProvider.GetRequiredService();
+ await keyCacheSvc.DeleteExpiredKeysAsync();
+}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Services/AuthService.cs b/Foxnouns.Backend/Services/AuthService.cs
index 87494ed..0949a6f 100644
--- a/Foxnouns.Backend/Services/AuthService.cs
+++ b/Foxnouns.Backend/Services/AuthService.cs
@@ -3,6 +3,7 @@ using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace Foxnouns.Backend.Services;
@@ -30,6 +31,23 @@ public class AuthService(ILogger logger, DatabaseContext db, ISnowflakeGenerator
return user;
}
+ public async Task AuthenticateUserAsync(string email, string password)
+ {
+ var user = await db.Users.FirstOrDefaultAsync(u => u.AuthMethods.Any(a => a.AuthType == AuthType.Email && a.RemoteId == email));
+ if (user == null) throw new ApiError.NotFound("No user with that email address found, or password is incorrect");
+
+ var pwResult = await Task.Run(() => _passwordHasher.VerifyHashedPassword(user, user.Password!, password));
+ if (pwResult == PasswordVerificationResult.Failed)
+ throw new ApiError.NotFound("No user with that email address found, or password is incorrect");
+ if (pwResult == PasswordVerificationResult.SuccessRehashNeeded)
+ {
+ user.Password = await Task.Run(() => _passwordHasher.HashPassword(user, password));
+ await db.SaveChangesAsync();
+ }
+
+ return user;
+ }
+
public (string, Token) GenerateToken(User user, Application application, string[] scopes, Instant expires)
{
if (!OauthUtils.ValidateScopes(application, scopes))
diff --git a/Foxnouns.Backend/Services/KeyCacheService.cs b/Foxnouns.Backend/Services/KeyCacheService.cs
new file mode 100644
index 0000000..253cc9f
--- /dev/null
+++ b/Foxnouns.Backend/Services/KeyCacheService.cs
@@ -0,0 +1,51 @@
+using Foxnouns.Backend.Database;
+using Foxnouns.Backend.Database.Models;
+using Foxnouns.Backend.Utils;
+using Microsoft.EntityFrameworkCore;
+using NodaTime;
+
+namespace Foxnouns.Backend.Services;
+
+public class KeyCacheService(DatabaseContext db, IClock clock, ILogger logger)
+{
+ public Task SetKeyAsync(string key, string value, Duration expireAfter) =>
+ db.SetKeyAsync(key, value, clock.GetCurrentInstant() + expireAfter);
+
+ public async Task SetKeyAsync(string key, string value, Instant expires)
+ {
+ db.TemporaryKeys.Add(new TemporaryKey
+ {
+ Expires = expires,
+ Key = key,
+ Value = value,
+ });
+ await db.SaveChangesAsync();
+ }
+
+ public async Task GetKeyAsync(string key, bool delete = false)
+ {
+ var value = await db.TemporaryKeys.FirstOrDefaultAsync(k => k.Key == key);
+ if (value == null) return null;
+
+ if (delete)
+ {
+ await db.TemporaryKeys.Where(k => k.Key == key).ExecuteDeleteAsync();
+ await db.SaveChangesAsync();
+ }
+
+ return value.Value;
+ }
+
+ public async Task DeleteExpiredKeysAsync()
+ {
+ var count = await db.TemporaryKeys.Where(k => k.Expires < clock.GetCurrentInstant()).ExecuteDeleteAsync();
+ if (count != 0) logger.Information("Removed {Count} expired keys from the database", count);
+ }
+
+ public async Task GenerateAuthStateAsync()
+ {
+ var state = OauthUtils.RandomToken();
+ await SetKeyAsync($"oauth_state:{state}", "", Duration.FromMinutes(10));
+ return state;
+ }
+}
\ No newline at end of file
diff --git a/Foxnouns.Backend/Services/UserRendererService.cs b/Foxnouns.Backend/Services/UserRendererService.cs
index 0f22021..0ac7f90 100644
--- a/Foxnouns.Backend/Services/UserRendererService.cs
+++ b/Foxnouns.Backend/Services/UserRendererService.cs
@@ -7,7 +7,7 @@ namespace Foxnouns.Backend.Services;
public class UserRendererService(DatabaseContext db, MemberRendererService memberRendererService)
{
- public async Task