From b4ce37071bdfa569e25e0b3152355c3564149cad Mon Sep 17 00:00:00 2001 From: saddiqs1 Date: Thu, 11 Jan 2024 17:16:28 +0000 Subject: [PATCH] Feature/crosshair manager (#2) * migrations system setup * updated types location, github actions defined * auth flow complete * env badge, updated packages, manager page, simplified home page, layout * user context, logout button * crosshair contains name * scale taken into account for xhair calculations * outline takes alpha into account, update style of xhairpreview, start of manager page * useCrosshair hook * text overflow handled, addcrosshaircard component made * frontend for crosshair form done * crosshair commands moved to utils, updated styling of preview card, usercrosshairs component, useCrosshair hook, crosshair endpoint, user type moved, DBTypes * edit/delete form done * created_at for crosshairs --- .env example | 8 +- .github/workflows/main-deploy.yml | 47 + .github/workflows/pr.yml | 28 + .gitignore | 1 + README.md | 54 + database/database.ts | 27 + database/index.ts | 182 +++ database/migrations/20240110162746945_init.ts | 32 + database/seed.ts | 30 + database/utils.ts | 59 + package-lock.json | 1428 ++++++++++++++++- package.json | 43 +- scripts/copyFile.js | 18 + scripts/createMigration.js | 29 + src/components/AddCrosshairCard.tsx | 46 + src/components/AddCrosshairForm.tsx | 89 + src/components/CrosshairConverter.tsx | 26 +- src/components/CrosshairPreview.tsx | 89 +- src/components/EditCrosshairsCard.tsx | 53 + src/components/EditCrosshairsForm.tsx | 186 +++ src/components/Layout/EnvironmentBadge.tsx | 28 + src/components/Layout/Header/Header.tsx | 51 + src/components/Layout/Header/HeaderLink.tsx | 45 + src/components/Layout/Header/LoginButton.tsx | 17 + src/components/Layout/Header/LogoutButton.tsx | 18 + src/components/RenderCrosshair.tsx | 279 ++-- src/components/UserCrosshairs.tsx | 35 + src/contexts/UserContext.ts | 9 + src/layouts/DefaultLayout.tsx | 21 + src/lib/auth/session.ts | 18 + src/lib/auth/steamAuth.ts | 7 + src/lib/crosshairUtils.ts | 73 +- src/lib/fetcher.ts | 7 + src/lib/hooks/useAddCrosshairPost.ts | 25 + src/lib/hooks/useCrosshair.ts | 22 + src/lib/hooks/useCrosshairPost.ts | 25 + src/lib/hooks/useEnvironment.ts | 18 + src/lib/hooks/useUser.ts | 17 + src/lib/kysely.ts | 30 + src/pages/_app.tsx | 21 +- src/pages/api/add-crosshair.ts | 56 + src/pages/api/auth/authenticate.ts | 56 + src/pages/api/auth/login.ts | 21 + src/pages/api/auth/logout.ts | 23 + src/pages/api/auth/user.ts | 59 + src/pages/api/crosshair.ts | 115 ++ src/pages/api/hello.ts | 13 - src/pages/index.tsx | 27 +- src/pages/manager.tsx | 80 + src/types/api-responses/AddCrosshair.ts | 4 + src/types/api-responses/Crosshair.ts | 16 + src/types/crosshair.ts | 39 + src/types/database.ts | 6 + src/types/generated/database.ts | 27 + src/types/user.ts | 5 + tsconfig-database.json | 11 + tsconfig.json | 1 + 57 files changed, 3414 insertions(+), 386 deletions(-) create mode 100644 .github/workflows/main-deploy.yml create mode 100644 .github/workflows/pr.yml create mode 100644 database/database.ts create mode 100644 database/index.ts create mode 100644 database/migrations/20240110162746945_init.ts create mode 100644 database/seed.ts create mode 100644 database/utils.ts create mode 100644 scripts/copyFile.js create mode 100644 scripts/createMigration.js create mode 100644 src/components/AddCrosshairCard.tsx create mode 100644 src/components/AddCrosshairForm.tsx create mode 100644 src/components/EditCrosshairsCard.tsx create mode 100644 src/components/EditCrosshairsForm.tsx create mode 100644 src/components/Layout/EnvironmentBadge.tsx create mode 100644 src/components/Layout/Header/Header.tsx create mode 100644 src/components/Layout/Header/HeaderLink.tsx create mode 100644 src/components/Layout/Header/LoginButton.tsx create mode 100644 src/components/Layout/Header/LogoutButton.tsx create mode 100644 src/components/UserCrosshairs.tsx create mode 100644 src/contexts/UserContext.ts create mode 100644 src/layouts/DefaultLayout.tsx create mode 100644 src/lib/auth/session.ts create mode 100644 src/lib/auth/steamAuth.ts create mode 100644 src/lib/fetcher.ts create mode 100644 src/lib/hooks/useAddCrosshairPost.ts create mode 100644 src/lib/hooks/useCrosshair.ts create mode 100644 src/lib/hooks/useCrosshairPost.ts create mode 100644 src/lib/hooks/useEnvironment.ts create mode 100644 src/lib/hooks/useUser.ts create mode 100644 src/lib/kysely.ts create mode 100644 src/pages/api/add-crosshair.ts create mode 100644 src/pages/api/auth/authenticate.ts create mode 100644 src/pages/api/auth/login.ts create mode 100644 src/pages/api/auth/logout.ts create mode 100644 src/pages/api/auth/user.ts create mode 100644 src/pages/api/crosshair.ts delete mode 100644 src/pages/api/hello.ts create mode 100644 src/pages/manager.tsx create mode 100644 src/types/api-responses/AddCrosshair.ts create mode 100644 src/types/api-responses/Crosshair.ts create mode 100644 src/types/crosshair.ts create mode 100644 src/types/database.ts create mode 100644 src/types/generated/database.ts create mode 100644 src/types/user.ts create mode 100644 tsconfig-database.json diff --git a/.env example b/.env example index 4d50ed4..2f5c769 100644 --- a/.env example +++ b/.env example @@ -1,2 +1,6 @@ -NEXT_PUBLIC_CLIENT_VAR= -SERVER_VAR= \ No newline at end of file +DATABASE_URL="postgresql://user:password@localhost:5432/cs-crosshairs" +NODE_ENV="development" # DO NOT CHANGE THIS + +NEXT_PUBLIC_DOMAIN=http://localhost:3000 +STEAM_API_KEY= # Get from https://steamcommunity.com/dev/apikey +SESSION_SECRET=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 \ No newline at end of file diff --git a/.github/workflows/main-deploy.yml b/.github/workflows/main-deploy.yml new file mode 100644 index 0000000..a1f824d --- /dev/null +++ b/.github/workflows/main-deploy.yml @@ -0,0 +1,47 @@ +name: Deploy + +on: + push: + branches: + - main + +jobs: + migrate-database: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install dependencies + run: npm ci + + - name: Run Database Migration + run: npm run migrate + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + NODE_ENV: 'production' + + build-and-deploy-to-vercel: + runs-on: ubuntu-latest + needs: migrate-database + + steps: + - uses: actions/checkout@v2 + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..d3f2adb --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,28 @@ +name: PR Created + +on: + pull_request: + branches: + - main + +jobs: + lint-and-build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install dependencies + run: npm ci + + - name: Lint code + run: npm run lint + + - name: Build App + run: npm run build diff --git a/.gitignore b/.gitignore index 2b3d9af..979fdfa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +/distDatabase # testing /coverage diff --git a/README.md b/README.md index 0704bba..f0e4199 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,63 @@ This is a basic [Next.js](https://nextjs.org/) template with [Mantine Core & Not 2. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## Database Commands + +- Create a new migration + + ```bash + npm run migrate:create migration-name + ``` + +- Run all migrations (_NOTE: This will run migrations that have not yet been run_) + + ```bash + npm run migrate + ``` + +- Generate your database types. You should run this everytime after you have run a new migration. + + ```bash + npm run db:generate + ``` + +- Run migrations from fresh i.e. after deleting everything in the database (_NOTE: This will nuke your database, and run all migrations_) + + ```bash + npm run migrate:fresh + ``` + +- Run a single migration + + ```bash + npm run migrate:up + ``` + +- Revert a single migration (_NOTE: This will only revert a single migration at a time. Mainly useful for development_) + + ```bash + npm run migrate:down + ``` + +- Seed your database (_NOTE: This will insert example data into your database. Mainly useful for development_) + + ```bash + npm run db:seed + ``` + +- Migrate fresh and seed the db in one command. Useful for the dev environment. + + ```bash + npm run dev:reset-db + ``` + ### TODO - [ ] Fix `csgo-sharecode` package import - [ ] Show crosshair style within preview - [ ] toggle between 16:9 vs 4:3 for crosshair preview - [ ] allow user to save crosshair +- [ ] validation on converter page + +- [ ] create login +- [ ] create ui for entering in and saving crosshairs diff --git a/database/database.ts b/database/database.ts new file mode 100644 index 0000000..f2df928 --- /dev/null +++ b/database/database.ts @@ -0,0 +1,27 @@ +import type { ColumnType } from "kysely"; + +export type Generated = T extends ColumnType + ? ColumnType + : ColumnType; + +export type Timestamp = ColumnType; + +export interface Crosshairs { + created_at: Generated; + crosshair: string; + id: Generated; + name: string; + user_id: number | null; +} + +export interface Users { + created_at: Generated; + id: Generated; + latest_login_at: Timestamp | null; + steam_uid: string; +} + +export interface DB { + crosshairs: Crosshairs; + users: Users; +} diff --git a/database/index.ts b/database/index.ts new file mode 100644 index 0000000..0ab3a99 --- /dev/null +++ b/database/index.ts @@ -0,0 +1,182 @@ +import * as path from 'path'; +import { promises as fs } from 'fs'; +import { Kysely, Migrator, FileMigrationProvider, sql } from 'kysely'; +import chalk from 'chalk'; +import { Env, getDb, getEnv } from './utils'; + +function getMigrator(db: Kysely) { + const migrator = new Migrator({ + db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: path.join(__dirname, 'migrations'), + }), + }); + + return migrator; +} + +async function migrateFresh(env: Env) { + const db = getDb(env); + const migrator = getMigrator(db); + + // Deletes all tables from database. + await sql` + DO $$ + DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + END $$; + `.execute(db); + + const { error, results } = await migrator.migrateToLatest(); + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log( + chalk.greenBright( + `✔ Migration "${it.migrationName}" was executed successfully!` + ) + ); + } else if (it.status === 'Error') { + console.error( + chalk.redBright(`❗ Failed to execute migration "${it.migrationName}"`) + ); + } + }); + + if (error) { + console.error(chalk.redBright(`❌ Migrations have failed`)); + console.error(error); + process.exit(1); + } + + await db.destroy(); +} + +async function migrateToLatest(env: Env) { + const db = getDb(env); + const migrator = getMigrator(db); + + const { error, results } = await migrator.migrateToLatest(); + + if (results?.length === 0 && !error) { + console.log(chalk.blueBright(`No migrations were executed.`)); + await db.destroy(); + return; + } + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log( + chalk.greenBright( + `✔ Migration "${it.migrationName}" was executed successfully!` + ) + ); + } else if (it.status === 'Error') { + console.error( + chalk.redBright(`❗ Failed to execute migration "${it.migrationName}"`) + ); + } + }); + + if (error) { + console.error(chalk.redBright(`❌ Migrations have failed`)); + console.error(error); + process.exit(1); + } + + await db.destroy(); +} + +async function migrateUp(env: Env) { + const db = getDb(env); + const migrator = getMigrator(db); + + const { error, results } = await migrator.migrateUp(); + + if (results?.length === 0 && !error) { + console.log(chalk.blueBright(`No migrations were executed.`)); + await db.destroy(); + return; + } + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log( + chalk.greenBright( + `✔ Migration "${it.migrationName}" was executed successfully!` + ) + ); + } else if (it.status === 'Error') { + console.error( + chalk.redBright(`❗ Failed to execute migration "${it.migrationName}"`) + ); + } + }); + + if (error) { + console.error(chalk.redBright(`❌ Migrations have failed`)); + console.error(error); + process.exit(1); + } + + await db.destroy(); +} + +async function migrateDown(env: Env) { + const db = getDb(env); + const migrator = getMigrator(db); + + const { error, results } = await migrator.migrateDown(); + + if (results?.length === 0 && !error) { + console.log(chalk.blueBright(`No migrations were executed.`)); + await db.destroy(); + return; + } + + results?.forEach((it) => { + if (it.status === 'Success') { + console.log( + chalk.greenBright( + `✔ Migration "${it.migrationName}" was reverted successfully!` + ) + ); + } else if (it.status === 'Error') { + console.error( + chalk.redBright(`❗ Failed to revert migration "${it.migrationName}"`) + ); + } + }); + + if (error) { + console.error(chalk.redBright(`❌ Migrations have failed`)); + console.error(error); + process.exit(1); + } + + await db.destroy(); +} + +const env = getEnv(); + +if (process.argv[2] === 'down') { + console.log(chalk.blueBright('Migrating Down...')); + migrateDown(env); +} else if (process.argv[2] === 'up') { + console.log(chalk.blueBright('Migrating Up...')); + migrateUp(env); +} else if (process.argv[2] === 'fresh') { + console.log(chalk.blueBright('Migrating Fresh...')); + migrateFresh(env); +} else if (process.argv[2] === 'latest') { + console.log(chalk.blueBright('Migrating To Latest...')); + migrateToLatest(env); +} else { + console.log(chalk.grey('Unknown command')); +} diff --git a/database/migrations/20240110162746945_init.ts b/database/migrations/20240110162746945_init.ts new file mode 100644 index 0000000..bfd7b88 --- /dev/null +++ b/database/migrations/20240110162746945_init.ts @@ -0,0 +1,32 @@ +import { Kysely, sql } from 'kysely' +import { DB } from '../database' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('users') + .ifNotExists() + .addColumn('id', 'serial', (col) => col.primaryKey().notNull()) + .addColumn('steam_uid', 'text', (col) => col.unique().notNull()) + .addColumn('created_at', 'timestamp', (col) => + col.defaultTo(sql`now()`).notNull() + ) + .addColumn('latest_login_at', 'timestamp') + .execute() + + await db.schema + .createTable('crosshairs') + .ifNotExists() + .addColumn('id', 'serial', (col) => col.primaryKey().notNull()) + .addColumn('user_id', 'integer', (col) => col.references('users.id')) + .addColumn('crosshair', 'text', (col) => col.notNull()) + .addColumn('name', 'text', (col) => col.notNull()) + .addColumn('created_at', 'timestamp', (col) => + col.defaultTo(sql`now()`).notNull() + ) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('crosshairs').ifExists().execute() + await db.schema.dropTable('users').ifExists().execute() +} diff --git a/database/seed.ts b/database/seed.ts new file mode 100644 index 0000000..73c4f0d --- /dev/null +++ b/database/seed.ts @@ -0,0 +1,30 @@ +import chalk from 'chalk' +import { getDb, getEnv } from './utils' +import { Kysely } from 'kysely' +import { DB } from './database' + +async function main(db: Kysely) { + console.log(chalk.gray(`Seeding currently unimplemented...`)) + + /* + TODO: Implement below. Take developer prompt to create a user, with either preset crosshair options or allow them to enter in some. + + console.log(chalk.blue(`Starting Seed...`)) + + await db.transaction().execute(async (trx) => { + // Any seeding happens here. + }) + + console.log(chalk.greenBright(`Finished Seeding!`)) + */ +} + +const env = getEnv() +const db = getDb(env) +main(db) + .then(async () => await db.destroy()) + .catch(async (e) => { + console.error(e) + await db.destroy() + process.exit(1) + }) diff --git a/database/utils.ts b/database/utils.ts new file mode 100644 index 0000000..4d73489 --- /dev/null +++ b/database/utils.ts @@ -0,0 +1,59 @@ +import { Pool } from 'pg' +import dotenv from 'dotenv' +import { Kysely, PostgresDialect } from 'kysely' +import chalk from 'chalk' +import { DB } from './database' +import { createKysely } from '@vercel/postgres-kysely' + +export type Env = { + databaseUrl: string + appEnv?: string +} + +export function getEnv(): Env { + dotenv.config({ path: '.env.local' }) + + const { DATABASE_URL, NODE_ENV } = process.env + + if (typeof DATABASE_URL !== 'string') + throw Error( + `databaseUrl is set to an incorrect value of ${DATABASE_URL}` + ) + + if (NODE_ENV !== 'production' && !DATABASE_URL.includes('localhost')) { + const error = `CANNOT INTERACT WITH DB ${DATABASE_URL} IN ENVIRONMENT ${NODE_ENV} LOCALLY` + console.error(chalk.redBright(error)) + throw new Error(error) + } + + return { + databaseUrl: DATABASE_URL, + appEnv: NODE_ENV, + } +} + +export function getDb(env: Env) { + console.log(chalk.blueBright(`Connecting to ${env.databaseUrl}...`)) + let db: Kysely | undefined = undefined + + if (env.appEnv === 'production') { + db = createKysely({ + connectionString: env.databaseUrl, + }) + Object.defineProperty( + db.getExecutor().adapter, + 'supportsTransactionalDdl', + () => false + ) + } else { + db = new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + connectionString: env.databaseUrl, + }), + }), + }) + } + + return db +} diff --git a/package-lock.json b/package-lock.json index c26d45a..f4f2d8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,24 +10,38 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/server": "^11.11.0", - "@mantine/core": "^6.0.19", - "@mantine/hooks": "^6.0.19", - "@mantine/next": "^6.0.19", - "@mantine/notifications": "^6.0.19", - "@types/node": "20.5.9", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@mantine/core": "^6.0.21", + "@mantine/form": "^6.0.21", + "@mantine/hooks": "^6.0.21", + "@mantine/next": "^6.0.21", + "@mantine/notifications": "^6.0.21", + "@tabler/icons-react": "^2.45.0", + "@vercel/postgres": "^0.5.1", + "@vercel/postgres-kysely": "^0.6.0", "csgo-sharecode": "^3.1.0", "eslint": "8.48.0", "eslint-config-next": "13.4.19", + "iron-session": "^8.0.1", + "kysely": "^0.26.3", "next": "13.4.19", + "node-steam-openid": "^1.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "5.2.2" + "swr": "^2.2.4", + "typescript": "5.2.2", + "zod": "^3.22.4" }, "devDependencies": { + "@types/node": "20.5.9", + "@types/node-steam-openid": "^1.0.3", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "chalk": "^4.1.2", + "dotenv-cli": "^7.3.0", "eslint-config-prettier": "^9.0.0", - "prettier": "^3.0.3" + "kysely-codegen": "^0.11.0", + "prettier": "^3.0.3", + "rimraf": "^5.0.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -494,39 +508,95 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@mantine/core": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.19.tgz", - "integrity": "sha512-SvMZCOgCc315SIg6hkuLM0ZnBaAac4VFDHZ0BM5LIE4MPJUpe4QOLsg/5RGxOa5s7JRCtu/dawH3/9frvfDrhw==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz", + "integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==", "dependencies": { "@floating-ui/react": "^0.19.1", - "@mantine/styles": "6.0.19", - "@mantine/utils": "6.0.19", + "@mantine/styles": "6.0.21", + "@mantine/utils": "6.0.21", "@radix-ui/react-scroll-area": "1.0.2", "react-remove-scroll": "^2.5.5", "react-textarea-autosize": "8.3.4" }, "peerDependencies": { - "@mantine/hooks": "6.0.19", + "@mantine/hooks": "6.0.21", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, + "node_modules/@mantine/form": { + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-6.0.21.tgz", + "integrity": "sha512-d4tlxyZic7MSDnaPx/WliCX1sRFDkUd2nxx4MxxO2T4OSek0YDqTlSBCxeoveu60P+vrQQN5rbbsVsaOJBe4SQ==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "klona": "^2.0.5" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@mantine/hooks": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.19.tgz", - "integrity": "sha512-YkmuB6kmoenU1PVuE8tLBA+6RJIY9hIsGyIQG1yuPAy6SLWNFT8g2T9YvI/psqsUbVIYGaNEXg8zq42xbxnD8Q==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz", + "integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@mantine/next": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/next/-/next-6.0.19.tgz", - "integrity": "sha512-XzWIpIPU+I/nCoHo5P9XYgKEvDXs0GwZf8UgF1CpscoBK3b5x3y0gk612tnr1YZW46lcEuL9j7Ky745w3PPLNQ==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/next/-/next-6.0.21.tgz", + "integrity": "sha512-McaVZZsmUol3yY92mSJSgcMQKFST97pVxNtI7Z52YocyuTjPPFXmqxF/TFj24A7noh1wzvRCPjfd9HX66sY+iQ==", "dependencies": { - "@mantine/ssr": "6.0.19", - "@mantine/styles": "6.0.19" + "@mantine/ssr": "6.0.21", + "@mantine/styles": "6.0.21" }, "peerDependencies": { "next": "*", @@ -535,26 +605,26 @@ } }, "node_modules/@mantine/notifications": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.19.tgz", - "integrity": "sha512-Cr2y8g2nM8bUAP+JYcKdT+a3d+1awUd40EMrDMwb+yUXUSt1amZerYQ7qRuezqvBgiViy/HGnM4WfeF3sfWJRQ==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.21.tgz", + "integrity": "sha512-qsrqxuJHK8b67sf9Pfk+xyhvpf9jMsivW8vchfnJfjv7yz1lLvezjytMFp4fMDoYhjHnDPOEc/YFockK4muhOw==", "dependencies": { - "@mantine/utils": "6.0.19", + "@mantine/utils": "6.0.21", "react-transition-group": "4.4.2" }, "peerDependencies": { - "@mantine/core": "6.0.19", - "@mantine/hooks": "6.0.19", + "@mantine/core": "6.0.21", + "@mantine/hooks": "6.0.21", "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "node_modules/@mantine/ssr": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.19.tgz", - "integrity": "sha512-NS4t4brMeaBzM5LKk/1zTc2wpqgcUSMCfTmqlKUh278CjBW9Le9Uaq3MVV94c1Q9Sky6N2E1BTWRAXuG3EvkJA==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.21.tgz", + "integrity": "sha512-TVPiz7VxbBntT42UFg4LCRqsv6HM5nvL5d2jBBbFcg9oztJ/5KVGhrtWbu2+kpq/uWWOpmE0sKDs3HQ/qr1PdQ==", "dependencies": { - "@mantine/styles": "6.0.19", + "@mantine/styles": "6.0.21", "html-react-parser": "1.4.12" }, "peerDependencies": { @@ -565,9 +635,9 @@ } }, "node_modules/@mantine/styles": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.19.tgz", - "integrity": "sha512-0tg3Dvv/kxCc1mbQVFhZaIhlSbSbV1F/3xG0NRlP2DF23mw9088o5KaIXGKM6XkXU6OEt/f99nDCUHBk2ixtUg==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz", + "integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==", "dependencies": { "clsx": "1.1.1", "csstype": "3.0.9" @@ -584,13 +654,21 @@ "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" }, "node_modules/@mantine/utils": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.19.tgz", - "integrity": "sha512-duvtnaW1gDR2gnvUqnWhl6DMW7sN0HEWqS8Z/BbwaMi75U+Xp17Q72R9JtiIrxQbzsq+KvH9L9B/pxMVwbLirg==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz", + "integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==", "peerDependencies": { "react": ">=16.8.0" } }, + "node_modules/@neondatabase/serverless": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.6.0.tgz", + "integrity": "sha512-qXxBRYN0m2v8kVQBfMxbzNGn2xFAhTXFibzQlE++NfJ56Shz3m7+MyBBtXDlEH+3Wfa6lToDXf1MElocY4sJ3w==", + "dependencies": { + "@types/pg": "8.6.6" + } + }, "node_modules/@next/env": { "version": "13.4.19", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", @@ -771,6 +849,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@radix-ui/number": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz", @@ -915,30 +1003,143 @@ "tslib": "^2.4.0" } }, + "node_modules/@tabler/icons": { + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.45.0.tgz", + "integrity": "sha512-J10UDghOni9wlrj5CpKAzychDCABCKYq897mGg0wGFsd+tYLaUdz0dt/HZeGnV8gZJo0hIiTPLGwBp5EW42Qsg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.45.0.tgz", + "integrity": "sha512-1vSBsHnBi9AfMILeJQrQo1XIHtFOxuWNGOeIvNHpDcBXyFTfVvDuh64PjMl57xCh5y/PlQlu3Hpx9vSkpSYXYQ==", + "dependencies": { + "@tabler/icons": "2.45.0", + "prop-types": "^15.7.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, "node_modules/@types/node": { "version": "20.5.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==" }, + "node_modules/@types/node-steam-openid": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/node-steam-openid/-/node-steam-openid-1.0.3.tgz", + "integrity": "sha512-nZ9Teu9uW8HFARbG17CU08hoofMjLP9haYT0J2zR5cfIOTgZRO3Oclr3sQlvg1G4jt6H+2H2C+e4Jhzr4Yq9kg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/pg": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", + "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "devOptional": true + }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -949,6 +1150,7 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -956,7 +1158,29 @@ "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "devOptional": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } }, "node_modules/@typescript-eslint/parser": { "version": "6.5.0", @@ -1055,6 +1279,34 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vercel/postgres": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.5.1.tgz", + "integrity": "sha512-JKl8QOBIDnifhkxAhIKtY0A5Tb8oWBf2nzZhm0OH7Ffjsl0hGVnDL2w1/FCfpX8xna3JAWM034NGuhZfTFdmiw==", + "dependencies": { + "@neondatabase/serverless": "0.6.0", + "bufferutil": "4.0.8", + "utf-8-validate": "6.0.3", + "ws": "8.14.2" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@vercel/postgres-kysely": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@vercel/postgres-kysely/-/postgres-kysely-0.6.0.tgz", + "integrity": "sha512-R5iSb7aM0XDztcOXfBaBBgdIHLtn0auFCXXJA6gq0GCWSZyBTv4OawsIWxix4lSjqql/MKmze7zvAVP21UkxMg==", + "dependencies": { + "@vercel/postgres": "0.5.1" + }, + "engines": { + "node": ">=14.6" + }, + "peerDependencies": { + "kysely": "^0.24.2 || ^0.25.0 || ^0.26.0" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1269,6 +1521,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1288,6 +1545,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -1340,6 +1607,29 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1434,6 +1724,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1444,6 +1745,14 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -1531,6 +1840,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1544,6 +1861,15 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1634,6 +1960,42 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotenv-cli": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.3.0.tgz", + "integrity": "sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -1669,6 +2031,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2335,11 +2703,44 @@ "node": ">=12.0.0" } }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2348,6 +2749,35 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2431,57 +2861,144 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/git-diff": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/git-diff/-/git-diff-2.0.6.tgz", + "integrity": "sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "chalk": "^2.3.2", + "diff": "^3.5.0", + "loglevel": "^1.6.1", + "shelljs": "^0.8.1", + "shelljs.exec": "^1.1.7" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 4.8.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/git-diff/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=4" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "node_modules/git-diff/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/globalthis": { - "version": "1.0.3", + "node_modules/git-diff/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/git-diff/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/git-diff/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/git-diff/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-diff/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dependencies": { @@ -2735,6 +3252,15 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -2743,6 +3269,28 @@ "loose-envify": "^1.0.0" } }, + "node_modules/iron-session": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.1.tgz", + "integrity": "sha512-ZQKazQRn/J5YaXY+CQ69V9lx9boh4swl+BgnNC1knxyZgBjhiXuGpY60URRntMPib9QdrsN9qAOTqdMFMbGYXg==", + "funding": [ + "https://github.com/sponsors/vvo", + "https://github.com/sponsors/brc-dd" + ], + "dependencies": { + "cookie": "0.6.0", + "iron-webcrypto": "1.0.0", + "uncrypto": "0.1.3" + } + }, + "node_modules/iron-webcrypto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz", + "integrity": "sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg==", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -2856,6 +3404,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -3058,6 +3615,24 @@ "reflect.getprototypeof": "^1.0.3" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3127,6 +3702,62 @@ "json-buffer": "3.0.1" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/kysely": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.26.3.tgz", + "integrity": "sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/kysely-codegen": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/kysely-codegen/-/kysely-codegen-0.11.0.tgz", + "integrity": "sha512-8aklzXygjANshk5BoGSQ0BWukKIoPL4/k1iFWyteGUQ/VtB1GlyrELBZv1GglydjLGECSSVDpsOgEXyWQmuksg==", + "dev": true, + "dependencies": { + "chalk": "4.1.2", + "dotenv": "^16.0.3", + "git-diff": "^2.0.6", + "micromatch": "^4.0.5", + "minimist": "^1.2.8" + }, + "bin": { + "kysely-codegen": "dist/cli/bin.js" + }, + "peerDependencies": { + "@libsql/kysely-libsql": "^0.3.0", + "better-sqlite3": ">=7.6.2", + "kysely": ">=0.19.12", + "mysql2": "^2.3.3 || ^3.0.0", + "pg": "^8.8.0" + }, + "peerDependenciesMeta": { + "@libsql/kysely-libsql": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "kysely": { + "optional": false + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + } + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -3176,6 +3807,19 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/loglevel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3218,6 +3862,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3237,6 +3900,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3319,6 +3991,34 @@ } } }, + "node_modules/next/node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-steam-openid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-steam-openid/-/node-steam-openid-1.2.1.tgz", + "integrity": "sha512-uD7VIACH6WO+AWlHr2GGvIN0foao2IihP4AW5KaV5SwcLlI2DgtI7ddSczoTqo0chbeZmoAxjMRi6irVYeIsKg==", + "dependencies": { + "axios": "^1.6.0", + "openid": "^2.0.6", + "url": "^0.11.1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3436,6 +4136,18 @@ "wrappy": "1" } }, + "node_modules/openid": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/openid/-/openid-2.0.11.tgz", + "integrity": "sha512-JAq4HWu9W9CYk83KOP4f8zwGfwwMrFOz9AAlji7h1oLGag+/ZqyfqKror0/TTHM1OV2mBpqNAv8CAPIhEDWB2A==", + "dependencies": { + "axios": "^1.6.0", + "qs": "^6.5.2" + }, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -3480,6 +4192,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3537,6 +4257,31 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3545,6 +4290,103 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "dev": true, + "optional": true, + "peer": true, + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3584,6 +4426,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3622,6 +4507,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -3630,6 +4520,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3796,6 +4700,18 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -3878,14 +4794,64 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3989,6 +4955,32 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs.exec": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/shelljs.exec/-/shelljs.exec-1.1.8.tgz", + "integrity": "sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -4002,6 +4994,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4026,6 +5030,17 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.x" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -4039,6 +5054,71 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz", @@ -4110,6 +5190,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -4194,6 +5287,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", + "integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -4381,6 +5486,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4389,6 +5499,20 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -4467,6 +5591,26 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4570,11 +5714,125 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", @@ -4616,9 +5874,9 @@ } }, "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 1ae43cf..2eb589e 100644 --- a/package.json +++ b/package.json @@ -6,28 +6,51 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "db:build": "rimraf distDatabase && tsc --project tsconfig-database.json", + "db:generate": "dotenv -e .env.local -- kysely-codegen --dialect postgres --out-file ./src/types/generated/database.ts && node scripts/copyFile ./src/types/generated/database.ts ./database/database.ts", + "db:seed": "npm run db:build && node distDatabase/seed.js", + "migrate": "npm run db:build && node distDatabase/index.js latest", + "migrate:fresh": "npm run db:build && node distDatabase/index.js fresh", + "migrate:up": "npm run db:build && node distDatabase/index.js up", + "migrate:down": "npm run db:build && node distDatabase/index.js down", + "migrate:create": "node scripts/createMigration.js", + "dev:reset-db": "npm run db:build && node distDatabase/index.js fresh && node distDatabase/seed.js" }, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/server": "^11.11.0", - "@mantine/core": "^6.0.19", - "@mantine/hooks": "^6.0.19", - "@mantine/next": "^6.0.19", - "@mantine/notifications": "^6.0.19", - "@types/node": "20.5.9", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@mantine/core": "^6.0.21", + "@mantine/form": "^6.0.21", + "@mantine/hooks": "^6.0.21", + "@mantine/next": "^6.0.21", + "@mantine/notifications": "^6.0.21", + "@tabler/icons-react": "^2.45.0", + "@vercel/postgres": "^0.5.1", + "@vercel/postgres-kysely": "^0.6.0", "csgo-sharecode": "^3.1.0", "eslint": "8.48.0", "eslint-config-next": "13.4.19", + "iron-session": "^8.0.1", + "kysely": "^0.26.3", "next": "13.4.19", + "node-steam-openid": "^1.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "5.2.2" + "swr": "^2.2.4", + "typescript": "5.2.2", + "zod": "^3.22.4" }, "devDependencies": { + "@types/node": "20.5.9", + "@types/node-steam-openid": "^1.0.3", + "@types/react": "18.2.21", + "@types/react-dom": "18.2.7", + "chalk": "^4.1.2", + "dotenv-cli": "^7.3.0", "eslint-config-prettier": "^9.0.0", - "prettier": "^3.0.3" + "kysely-codegen": "^0.11.0", + "prettier": "^3.0.3", + "rimraf": "^5.0.5" } } diff --git a/scripts/copyFile.js b/scripts/copyFile.js new file mode 100644 index 0000000..679388a --- /dev/null +++ b/scripts/copyFile.js @@ -0,0 +1,18 @@ +const fs = require('fs') +const path = require('path') +const chalk = require('chalk') + +const source = process.argv[2] +const destination = process.argv[3] + +const sourcePath = path.join(__dirname, '..', source) +const destinationPath = path.join(__dirname, '..', destination) + +try { + fs.copyFileSync(sourcePath, destinationPath) + console.log( + chalk.green(`✓ File copied to ${destinationPath} successfully.`) + ) +} catch (err) { + console.error(chalk.red('Error copying the file:', err)) +} diff --git a/scripts/createMigration.js b/scripts/createMigration.js new file mode 100644 index 0000000..231ad34 --- /dev/null +++ b/scripts/createMigration.js @@ -0,0 +1,29 @@ +const fs = require('fs') +const path = require('path') +const chalk = require('chalk') + +const timestamp = new Date().toISOString().replace(/\D/g, '') // e.g. 2023-06-08T20:31:08.111Z --> 20230608203108111 +const migrationName = process.argv[2] +const migrationFileName = `${timestamp}_${migrationName}.ts` +const migrationContent = `import { Kysely } from 'kysely'; +import { DB } from "../database"; + +export async function up(db: Kysely): Promise { + // Migration Code +} + +export async function down(db: Kysely): Promise { + // Migration Code +} +` + +const migrationsDir = path.join(__dirname, '..', 'database', 'migrations') +const migrationFilePath = path.join(migrationsDir, migrationFileName) + +if (!fs.existsSync(migrationsDir)) { + fs.mkdirSync(migrationsDir, { recursive: true }) +} + +fs.writeFileSync(migrationFilePath, migrationContent) + +console.log(chalk.green(`✓ Created migration file: ${migrationFilePath}`)) diff --git a/src/components/AddCrosshairCard.tsx b/src/components/AddCrosshairCard.tsx new file mode 100644 index 0000000..4817c70 --- /dev/null +++ b/src/components/AddCrosshairCard.tsx @@ -0,0 +1,46 @@ +import { Flex, Modal, Stack, Text } from '@mantine/core' +import { IconCrosshair } from '@tabler/icons-react' +import { useDisclosure } from '@mantine/hooks' +import { AddCrosshairForm } from './AddCrosshairForm' + +type Props = {} + +export const AddCrosshairCard: React.FC = ({}) => { + const [opened, { open, close }] = useDisclosure(false) + + return ( + <> + ({ + border: `1px ${theme.colors.dark[1]} solid`, + borderRadius: theme.radius.md, + transition: 'shadow 150ms ease, transform 100ms ease;', + '&:hover': { + cursor: 'pointer', + boxShadow: theme.shadows.md, + transform: `scale(1.05)`, + background: theme.colors.dark[8], + }, + })} + onClick={open} + > + + + + + Add Crosshair + + + + Add Crosshair} + > + + + + ) +} diff --git a/src/components/AddCrosshairForm.tsx b/src/components/AddCrosshairForm.tsx new file mode 100644 index 0000000..0329b53 --- /dev/null +++ b/src/components/AddCrosshairForm.tsx @@ -0,0 +1,89 @@ +import { Button, Loader, Stack, TextInput } from '@mantine/core' +import { z } from 'zod' +import { useForm, zodResolver } from '@mantine/form' +import { useAddCrosshairPost } from '@lib/hooks/useAddCrosshairPost' +import { showNotification } from '@mantine/notifications' +import { IconCheck, IconCircleX, IconCrosshair } from '@tabler/icons-react' +import { useCrosshair } from '@lib/hooks/useCrosshair' + +type Props = { + onComplete: () => void +} + +const crosshairCodeRegex = + /CSGO-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}/ + +const formSchema = z.object({ + name: z.string(), + crosshairCode: z + .string() + .regex(crosshairCodeRegex, 'Crosshair code must be in a valid format.'), +}) + +export type AddCrosshairFormValues = z.infer + +export const AddCrosshairForm: React.FC = ({ onComplete }) => { + const { addCrosshair, isAddingCrosshair } = useAddCrosshairPost() + const { mutateCrosshairs } = useCrosshair() + const form = useForm({ + initialValues: { + name: '', + crosshairCode: '', + }, + validate: zodResolver(formSchema), + }) + + const handleSubmit = async (values: AddCrosshairFormValues) => { + const res = await addCrosshair(values) + showNotification({ + message: res?.message, + icon: res?.success ? ( + + ) : ( + + ), + color: res?.success ? 'green' : 'red', + }) + + mutateCrosshairs() + onComplete() + } + + const handleInvalidForm = (errors: typeof form.errors) => { + if (errors) { + showNotification({ + message: 'There is an error with the form', + color: 'red', + }) + } + } + + return ( +
+ + + + + +
+ ) +} diff --git a/src/components/CrosshairConverter.tsx b/src/components/CrosshairConverter.tsx index 3591e1b..a8f0cf1 100644 --- a/src/components/CrosshairConverter.tsx +++ b/src/components/CrosshairConverter.tsx @@ -1,8 +1,6 @@ -import { copy } from '@lib/copy' +import { copyCommands } from '@lib/crosshairUtils' import { Button, TextInput, Flex } from '@mantine/core' -import { notifications } from '@mantine/notifications' import { useState } from 'react' -const csgoSharecode = require('csgo-sharecode') type Props = {} @@ -10,27 +8,7 @@ export const CrosshairConverter: React.FC = ({}) => { const [crosshairCode, setCrosshairCode] = useState('') const onCopy = () => { - try { - const crosshairCommands = csgoSharecode - .crosshairToConVars( - csgoSharecode.decodeCrosshairShareCode(crosshairCode) - ) - .replaceAll('\n', ';') - - copy(crosshairCommands) - - notifications.show({ - title: 'Crosshair Copied', - message: 'Crosshair is copied to your clipboard', - color: 'green', - }) - } catch (error: any) { - notifications.show({ - title: 'Error Copying Crosshair', - message: 'Crosshair input is in an incorrect format', - color: 'red', - }) - } + copyCommands(crosshairCode) } return ( diff --git a/src/components/CrosshairPreview.tsx b/src/components/CrosshairPreview.tsx index fcce0c8..e1241fd 100644 --- a/src/components/CrosshairPreview.tsx +++ b/src/components/CrosshairPreview.tsx @@ -1,58 +1,53 @@ -import { copy } from '@lib/copy' -import { Box, Text } from '@mantine/core' -import { notifications } from '@mantine/notifications' -const csgoSharecode = require('csgo-sharecode') -import { Crosshair, RenderCrosshair } from './RenderCrosshair' +import { Box, Stack, Text } from '@mantine/core' +import { RenderCrosshair } from './RenderCrosshair' +import { copyCommands, getCrosshair } from '@lib/crosshairUtils' type Props = { crosshairCode: string + name: string } -export const CrosshairPreview: React.FC = ({ crosshairCode }) => { - const crosshair: Crosshair = - csgoSharecode.decodeCrosshairShareCode(crosshairCode) - - const onClick = () => { - try { - const crosshairCommands = csgoSharecode - .crosshairToConVars(crosshair) - .replaceAll('\n', ';') - - copy(crosshairCommands) - - notifications.show({ - title: 'Crosshair Copied', - message: 'Crosshair is copied to your clipboard', - color: 'green', - }) - } catch (error: any) { - notifications.show({ - title: 'Error Copying Crosshair', - message: 'Crosshair input is in an incorrect format', - color: 'red', - }) - } - } - +export const CrosshairPreview: React.FC = ({ crosshairCode, name }) => { return ( - + ({ + borderRadius: theme.radius.md, + transition: 'shadow 150ms ease, transform 100ms ease;', + '&:hover': { + cursor: 'pointer', + boxShadow: theme.shadows.md, + transform: `scale(1.05)`, + background: theme.colors.dark[4], + }, + })} + onClick={() => copyCommands(crosshairCode)} + title={`Copy ${name}`} + > - size: {crosshair.length} - thickness: {crosshair.thickness} - gap: {crosshair.gap} - - outline:{' '} - {(crosshair.outlineEnabled as boolean) === true - ? crosshair.outline - : 'false'} - - - dot: {(crosshair.centerDotEnabled as boolean).toString()} - - + {name.length > 0 ? ( + + {name} + + ) : ( + + )} + ) } diff --git a/src/components/EditCrosshairsCard.tsx b/src/components/EditCrosshairsCard.tsx new file mode 100644 index 0000000..23d17ad --- /dev/null +++ b/src/components/EditCrosshairsCard.tsx @@ -0,0 +1,53 @@ +import { Flex, Modal, Stack, Text } from '@mantine/core' +import { IconEdit } from '@tabler/icons-react' +import { useDisclosure } from '@mantine/hooks' +import { EditCrosshairsForm } from './EditCrosshairsForm' +import { DBTypes } from '@my-types/database' + +type Props = { + crosshairs: DBTypes['crosshairs'][] +} + +export const EditCrosshairsCard: React.FC = ({ crosshairs }) => { + const [opened, { open, close }] = useDisclosure(false) + + return ( + <> + ({ + border: `1px ${theme.colors.dark[1]} solid`, + borderRadius: theme.radius.md, + transition: 'shadow 150ms ease, transform 100ms ease;', + '&:hover': { + cursor: 'pointer', + boxShadow: theme.shadows.md, + transform: `scale(1.05)`, + background: theme.colors.dark[8], + }, + })} + onClick={open} + > + + + + + Edit/Delete + + + + Edit/Delete} + size={'auto'} + > + + + + ) +} diff --git a/src/components/EditCrosshairsForm.tsx b/src/components/EditCrosshairsForm.tsx new file mode 100644 index 0000000..4df758f --- /dev/null +++ b/src/components/EditCrosshairsForm.tsx @@ -0,0 +1,186 @@ +import { + ActionIcon, + Button, + Flex, + Loader, + Stack, + TextInput, +} from '@mantine/core' +import { z } from 'zod' +import { useForm, zodResolver } from '@mantine/form' +import { useCrosshairPost } from '@lib/hooks/useCrosshairPost' +import { showNotification } from '@mantine/notifications' +import { + IconCheck, + IconCircleX, + IconCrosshair, + IconTrash, + IconTrashOff, +} from '@tabler/icons-react' +import { useCrosshair } from '@lib/hooks/useCrosshair' +import { DBTypes } from '@my-types/database' + +type Props = { + onComplete: () => void + crosshairs: DBTypes['crosshairs'][] +} + +const crosshairCodeRegex = + /CSGO-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}-[A-Za-z0-9]{5}/ + +const formSchema = z.object({ + crosshairs: z.array( + z.object({ + id: z.optional(z.number()), + name: z.string(), + crosshair: z + .string() + .regex( + crosshairCodeRegex, + 'Crosshair code must be in a valid format.' + ), + user_id: z.number().nullable(), + }) + ), + crosshairsToDelete: z.array(z.number()), +}) + +export type EditCrosshairFormValues = z.infer + +export const EditCrosshairsForm: React.FC = ({ + onComplete, + crosshairs, +}) => { + const { updateCrosshairs, isCrosshairsUpdating } = useCrosshairPost() + const { mutateCrosshairs } = useCrosshair() + const form = useForm({ + initialValues: { + crosshairs, + crosshairsToDelete: [], + }, + validate: zodResolver(formSchema), + }) + + const handleSubmit = async (values: EditCrosshairFormValues) => { + const crosshairs = values.crosshairs.filter( + (r) => !r.id || values.crosshairsToDelete.indexOf(r.id) === -1 + ) + + const res = await updateCrosshairs({ + crosshairs, + crosshairsToDelete: values.crosshairsToDelete, + }) + showNotification({ + message: res?.message, + icon: res?.success ? ( + + ) : ( + + ), + color: res?.success ? 'green' : 'red', + }) + + mutateCrosshairs() + onComplete() + } + + const handleInvalidForm = (errors: typeof form.errors) => { + if (errors) { + showNotification({ + message: 'There is an error with the form', + color: 'red', + }) + } + } + + const isSelectedForDeletion = (id?: number) => { + for (const val of form.values.crosshairsToDelete) { + if (val === id) { + return true + } + } + + return false + } + + const deleteCrosshair = (val: boolean, i: number) => { + if (val) { + form.insertListItem( + 'crosshairsToDelete', + form.getInputProps(`crosshairs.${i}.id`).value + ) + } else { + form.setFieldValue( + 'crosshairsToDelete', + form.values.crosshairsToDelete.filter( + (number) => + number !== + form.getInputProps(`crosshairs.${i}.id`).value + ) + ) + } + } + + return ( +
+ + {form.values.crosshairs.map((c, i) => ( + ({ + background: isSelectedForDeletion(c.id) + ? theme.fn.rgba(theme.colors.red[9], 0.3) + : theme.colors.dark[6], + borderRadius: theme.radius.md, + border: `1px ${theme.colors.dark[4]} solid`, + })} + > + + + + deleteCrosshair(!isSelectedForDeletion(c.id), i) + } + size={'xl'} + variant={'outline'} + > + {isSelectedForDeletion(c.id) ? ( + + ) : ( + + )} + + + ))} + + +
+ ) +} diff --git a/src/components/Layout/EnvironmentBadge.tsx b/src/components/Layout/EnvironmentBadge.tsx new file mode 100644 index 0000000..eaa89e2 --- /dev/null +++ b/src/components/Layout/EnvironmentBadge.tsx @@ -0,0 +1,28 @@ +import { useEnvironment } from '@lib/hooks/useEnvironment'; +import { Badge, Affix } from '@mantine/core'; + +type Props = {}; + +const EnvironmentBadge: React.FC = ({}) => { + const { env } = useEnvironment(); + + return ( + + + {env.label} + + + ); +}; + +export default EnvironmentBadge; diff --git a/src/components/Layout/Header/Header.tsx b/src/components/Layout/Header/Header.tsx new file mode 100644 index 0000000..ed971fe --- /dev/null +++ b/src/components/Layout/Header/Header.tsx @@ -0,0 +1,51 @@ +// Taken from https://ui.mantine.dev/component/header-simple/ + +import { Group, Title, Header as MantineHeader, Container } from '@mantine/core' +import { IconViewfinder } from '@tabler/icons-react' +import { HeaderLink } from './HeaderLink' +import { useRouter } from 'next/router' +import { LoginButton } from './LoginButton' +import { useContext } from 'react' +import { UserContext } from '@contexts/UserContext' +import { UserAvatar } from './LogoutButton' + +const LINKS = [ + { link: '/', label: 'Converter' }, + { link: '/manager', label: 'Manager' }, +] + +type Props = {} + +export const Header: React.FC = ({}) => { + const router = useRouter() + const { user, isLoading } = useContext(UserContext) + + return ( + + + + + CS2 Crosshair App + + + {LINKS.map((link, i) => ( + + ))} + {user && !isLoading ? : } + + + + ) +} diff --git a/src/components/Layout/Header/HeaderLink.tsx b/src/components/Layout/Header/HeaderLink.tsx new file mode 100644 index 0000000..d6cef9c --- /dev/null +++ b/src/components/Layout/Header/HeaderLink.tsx @@ -0,0 +1,45 @@ +import { UnstyledButton, Text } from '@mantine/core' +import Link from 'next/link' + +type Props = { + href: string + label: string + isActive?: boolean +} + +export const HeaderLink: React.FC = ({ href, label, isActive }) => { + const button = ( + ({ + color: isActive ? '#fff' : theme.colors.dark[0], + background: isActive ? theme.colors.blue[6] : undefined, + fontSize: theme.fontSizes.sm, + borderRadius: theme.radius.sm, + '&:hover': !isActive && { + cursor: 'pointer', + background: theme.colors.dark[6], + }, + })} + px={12} + py={8} + h={'100%'} + > + + {label} + + + ) + + if (href.startsWith('/')) + return ( + + {button} + + ) + + return ( + + {button} + + ) +} diff --git a/src/components/Layout/Header/LoginButton.tsx b/src/components/Layout/Header/LoginButton.tsx new file mode 100644 index 0000000..53ec4e2 --- /dev/null +++ b/src/components/Layout/Header/LoginButton.tsx @@ -0,0 +1,17 @@ +import { Button } from '@mantine/core' +import { IconBrandSteam } from '@tabler/icons-react' + +type Props = {} + +export const LoginButton: React.FC = ({}) => { + return ( + + ) +} diff --git a/src/components/Layout/Header/LogoutButton.tsx b/src/components/Layout/Header/LogoutButton.tsx new file mode 100644 index 0000000..b1165b3 --- /dev/null +++ b/src/components/Layout/Header/LogoutButton.tsx @@ -0,0 +1,18 @@ +import { Button } from '@mantine/core' +import { IconLogout } from '@tabler/icons-react' + +type Props = {} + +export const UserAvatar: React.FC = ({}) => { + return ( + + ) +} diff --git a/src/components/RenderCrosshair.tsx b/src/components/RenderCrosshair.tsx index cea4cd5..dafe70d 100644 --- a/src/components/RenderCrosshair.tsx +++ b/src/components/RenderCrosshair.tsx @@ -1,217 +1,160 @@ -import { Box } from '@mantine/core' import { getCrosshairValues } from '@lib/crosshairUtils' +import { Crosshair } from '@my-types/crosshair' type Props = { crosshair: Crosshair size: number - onClick: () => void } -export const RenderCrosshair: React.FC = ({ - crosshair, - size, - onClick, -}) => { +export const RenderCrosshair: React.FC = ({ crosshair, size }) => { const { crosshairLength, crosshairWidth, outlineEnabled, crosshairGap, color, + outlineColour, outlineThickness, centerDotEnabled, tStyleEnabled, - } = getCrosshairValues(crosshair, size) + } = getCrosshairValues(crosshair, size / 100) const center = size / 2 // NOTE: The order of the groups in the code below are in a specific order for a reason, to mimic the order of priority for the lines of a crosshair in CS2. // i.e. from bottom to top, left line, right line, top line, bottom line and then dot. return ( - - - {/* Left */} - - {outlineEnabled && ( - - )} + {/* Left */} + + {outlineEnabled && ( - + )} + + - {/* Right */} - - {outlineEnabled && ( - - )} + {/* Right */} + + {outlineEnabled && ( - + )} + + - {/* Top */} - - {outlineEnabled && ( - - )} + {/* Top */} + + {outlineEnabled && ( - + )} + + - {/* Bottom */} - - {outlineEnabled && ( - - )} + {/* Bottom */} + + {outlineEnabled && ( - + )} + + - {/* Dot */} - - {outlineEnabled && ( - - )} + {/* Dot */} + + {outlineEnabled && ( - - - + )} + + + ) } - -export interface Crosshair { - length: number - red: number - green: number - blue: number - gap: number - alphaEnabled: boolean - alpha: number - outlineEnabled: boolean - outline: number - color: number - thickness: number - centerDotEnabled: boolean - splitDistance: number - followRecoil: boolean - fixedCrosshairGap: number - innerSplitAlpha: number - outerSplitAlpha: number - splitSizeRatio: number - tStyleEnabled: boolean - deployedWeaponGapEnabled: boolean - /** - * CS:GO - * 0 => Default - * 1 => Default static - * 2 => Classic - * 3 => Classic dynamic - * 4 => Classic static - */ - /** - * CS2 - * 0 to 3 => Classic - * 4 => Classic static - * 5 => Legacy - */ - style: number -} diff --git a/src/components/UserCrosshairs.tsx b/src/components/UserCrosshairs.tsx new file mode 100644 index 0000000..e1b28f2 --- /dev/null +++ b/src/components/UserCrosshairs.tsx @@ -0,0 +1,35 @@ +import { Center, Flex, Loader, Stack } from '@mantine/core' +import { useCrosshair } from '@lib/hooks/useCrosshair' +import { CrosshairPreview } from './CrosshairPreview' +import { AddCrosshairCard } from './AddCrosshairCard' +import { EditCrosshairsCard } from './EditCrosshairsCard' + +type Props = {} + +export const UserCrosshairs: React.FC = () => { + const { crosshairs, isCrosshairsLoading } = useCrosshair() + + return ( + <> + {isCrosshairsLoading ? ( +
+ +
+ ) : ( + + {crosshairs.map((c, i) => ( + + ))} + + + + + + )} + + ) +} diff --git a/src/contexts/UserContext.ts b/src/contexts/UserContext.ts new file mode 100644 index 0000000..6fd9e35 --- /dev/null +++ b/src/contexts/UserContext.ts @@ -0,0 +1,9 @@ +import { User } from '@my-types/user' +import { createContext } from 'react' + +export interface IUserContext { + user?: User + isLoading: boolean +} + +export const UserContext = createContext(undefined as any) diff --git a/src/layouts/DefaultLayout.tsx b/src/layouts/DefaultLayout.tsx new file mode 100644 index 0000000..4613618 --- /dev/null +++ b/src/layouts/DefaultLayout.tsx @@ -0,0 +1,21 @@ +import { AppShell } from '@mantine/core' +import { useEnvironment } from '@lib/hooks/useEnvironment' +import EnvironmentBadge from '@components/Layout/EnvironmentBadge' +import { Header } from '@components/Layout/Header/Header' + +type Props = { + children: React.ReactElement +} + +const DefaultLayout: React.FC = ({ children }) => { + const { isLocal } = useEnvironment() + + return ( + }> + {children} + {isLocal && } + + ) +} + +export default DefaultLayout diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts new file mode 100644 index 0000000..57df5e1 --- /dev/null +++ b/src/lib/auth/session.ts @@ -0,0 +1,18 @@ +import { User } from '@my-types/user' +import { SessionOptions } from 'iron-session' + +export const sessionOptions: SessionOptions = { + password: process.env.SESSION_SECRET ?? '', + cookieName: 'cs-crosshairs', + cookieOptions: { + // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) + secure: process.env.NODE_ENV === 'production', + }, +} + +// This is where we specify the typings of req.session.* +declare module 'iron-session' { + interface IronSessionData { + user?: User + } +} diff --git a/src/lib/auth/steamAuth.ts b/src/lib/auth/steamAuth.ts new file mode 100644 index 0000000..8920bdb --- /dev/null +++ b/src/lib/auth/steamAuth.ts @@ -0,0 +1,7 @@ +import SteamAuth from 'node-steam-openid'; + +export const steamAuth = new SteamAuth({ + realm: `${process.env.NEXT_PUBLIC_DOMAIN}`, + returnUrl: `${process.env.NEXT_PUBLIC_DOMAIN}/api/auth/authenticate`, + apiKey: `${process.env.STEAM_API_KEY}`, +}); diff --git a/src/lib/crosshairUtils.ts b/src/lib/crosshairUtils.ts index cc41291..dc25371 100644 --- a/src/lib/crosshairUtils.ts +++ b/src/lib/crosshairUtils.ts @@ -1,4 +1,35 @@ -import { Crosshair } from 'csgo-sharecode' +import { copy } from '@lib/copy' +import { notifications } from '@mantine/notifications' +import { Crosshair } from '@my-types/crosshair' +const csgoSharecode = require('csgo-sharecode') + +export const copyCommands = (crosshairCode: string) => { + try { + const crosshairCommands = csgoSharecode + .crosshairToConVars( + csgoSharecode.decodeCrosshairShareCode(crosshairCode) + ) + .replaceAll('\n', ';') + + copy(crosshairCommands) + + notifications.show({ + title: 'Crosshair Copied', + message: 'Crosshair is copied to your clipboard', + color: 'green', + }) + } catch (error: any) { + notifications.show({ + title: 'Error Copying Crosshair', + message: 'Crosshair input is in an incorrect format', + color: 'red', + }) + } +} + +export const getCrosshair = (crosshairCode: string): Crosshair => { + return csgoSharecode.decodeCrosshairShareCode(crosshairCode) +} const getColor = ( colorCode: number, @@ -8,19 +39,26 @@ const getColor = ( alphaEnabled: boolean, alpha: number ) => { + let colour = '' + const alphaDecimal = alphaEnabled ? alpha / 255 : 1 + if (colorCode === 1) { - return `rgb(46, 250, 46, ${alphaEnabled ? alpha / 255 : 1})` + colour = `rgb(46, 250, 46, ${alphaDecimal})` } else if (colorCode === 2) { - return `rgb(250, 250, 46, ${alphaEnabled ? alpha / 255 : 1})` + colour = `rgb(250, 250, 46, ${alphaDecimal})` } else if (colorCode === 3) { - return `rgb(46, 46, 250, ${alphaEnabled ? alpha / 255 : 1})` + colour = `rgb(46, 46, 250, ${alphaDecimal})` } else if (colorCode === 4) { - return `rgb(46, 250, 250, ${alphaEnabled ? alpha / 255 : 1})` + colour = `rgb(46, 250, 250, ${alphaDecimal})` } else if (colorCode === 5) { - return `rgba(${r}, ${g}, ${b}, ${alphaEnabled ? alpha / 255 : 1})` + colour = `rgba(${r}, ${g}, ${b}, ${alphaDecimal})` + } else { + colour = `rgb(46, 250, 46, ${alphaDecimal})` } - return `rgb(46, 250, 46, ${alphaEnabled ? alpha / 255 : 1})` + const outlineColour = `rgb(0, 0, 0, ${alphaDecimal})` + + return { colour, outlineColour } } const getLength = (length: number) => { @@ -43,7 +81,7 @@ const getOutlineThickness = (outlineThickness: number) => { : Math.floor(outlineThickness) } -export const getCrosshairValues = (crosshair: Crosshair, size: number) => { +export const getCrosshairValues = (crosshair: Crosshair, scale: number) => { const { length, red, @@ -68,17 +106,26 @@ export const getCrosshairValues = (crosshair: Crosshair, size: number) => { style, } = crosshair - const crosshairLength = getLength(length) - const crosshairWidth = getThickness(thickness) * 2 - const crosshairGap = Math.floor(gap) + 4 - const outlineThickness = getOutlineThickness(outline) + const crosshairLength = getLength(length) * scale + const crosshairWidth = getThickness(thickness) * 2 * scale + const crosshairGap = (Math.floor(gap) + 4) * scale + const outlineThickness = getOutlineThickness(outline) * scale + const { colour, outlineColour } = getColor( + color, + red, + green, + blue, + alphaEnabled, + alpha + ) return { crosshairLength, crosshairWidth, outlineEnabled, crosshairGap, - color: getColor(color, red, green, blue, alphaEnabled, alpha), + color: colour, + outlineColour, outlineThickness, centerDotEnabled, tStyleEnabled, diff --git a/src/lib/fetcher.ts b/src/lib/fetcher.ts new file mode 100644 index 0000000..332d3aa --- /dev/null +++ b/src/lib/fetcher.ts @@ -0,0 +1,7 @@ +export default async function fetcher( + input: RequestInfo, + init?: RequestInit +): Promise { + const res = await fetch(input, init); + return res.json(); +} diff --git a/src/lib/hooks/useAddCrosshairPost.ts b/src/lib/hooks/useAddCrosshairPost.ts new file mode 100644 index 0000000..f837992 --- /dev/null +++ b/src/lib/hooks/useAddCrosshairPost.ts @@ -0,0 +1,25 @@ +import { AddCrosshairFormValues } from '@components/AddCrosshairForm' +import { PostAddCrosshairResponse } from '@my-types/api-responses/AddCrosshair' +import useSWRMutation from 'swr/mutation' + +async function postRequest( + url: RequestInfo, + { arg }: { arg: AddCrosshairFormValues } +) { + return fetch(url, { + method: 'POST', + body: JSON.stringify(arg), + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json() as Promise) +} + +export function useAddCrosshairPost() { + const { trigger, isMutating } = useSWRMutation( + `/api/add-crosshair`, + postRequest + ) + + return { addCrosshair: trigger, isAddingCrosshair: isMutating } +} diff --git a/src/lib/hooks/useCrosshair.ts b/src/lib/hooks/useCrosshair.ts new file mode 100644 index 0000000..13db644 --- /dev/null +++ b/src/lib/hooks/useCrosshair.ts @@ -0,0 +1,22 @@ +import fetcher from '@lib/fetcher' +import { GetCrosshairResponse } from '@my-types/api-responses/Crosshair' +import { DBTypes } from '@my-types/database' +import useSWRImmutable from 'swr/immutable' + +export function useCrosshair() { + const { data, isLoading, mutate } = useSWRImmutable( + `/api/crosshair`, + fetcher + ) + + let crosshairs: DBTypes['crosshairs'][] = [] + if (data && data.success) { + crosshairs = data.message + } + + return { + crosshairs, + isCrosshairsLoading: isLoading, + mutateCrosshairs: mutate, + } +} diff --git a/src/lib/hooks/useCrosshairPost.ts b/src/lib/hooks/useCrosshairPost.ts new file mode 100644 index 0000000..0d70193 --- /dev/null +++ b/src/lib/hooks/useCrosshairPost.ts @@ -0,0 +1,25 @@ +import { EditCrosshairFormValues } from '@components/EditCrosshairsForm' +import { PostCrosshairResponse } from '@my-types/api-responses/Crosshair' +import useSWRMutation from 'swr/mutation' + +async function postRequest( + url: RequestInfo, + { arg }: { arg: EditCrosshairFormValues } +) { + return fetch(url, { + method: 'POST', + body: JSON.stringify(arg), + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json() as Promise) +} + +export function useCrosshairPost() { + const { trigger, isMutating } = useSWRMutation( + `/api/crosshair`, + postRequest + ) + + return { updateCrosshairs: trigger, isCrosshairsUpdating: isMutating } +} diff --git a/src/lib/hooks/useEnvironment.ts b/src/lib/hooks/useEnvironment.ts new file mode 100644 index 0000000..9cec042 --- /dev/null +++ b/src/lib/hooks/useEnvironment.ts @@ -0,0 +1,18 @@ +type Environment = { url: string; label: string; color: string } + +const LOCAL = { url: 'http://localhost:3000', label: 'Local', color: 'yellow' } +const PROD = { + url: 'https://cs-crosshairs.vercel.app/', + label: 'Prod', + color: 'green', +} + +const ENVIRONMENTS: Environment[] = [LOCAL, PROD] + +export function useEnvironment() { + const env = + ENVIRONMENTS.find((e) => e.url === process.env.NEXT_PUBLIC_DOMAIN) ?? + LOCAL + + return { env, isLocal: env.label === 'Local' } +} diff --git a/src/lib/hooks/useUser.ts b/src/lib/hooks/useUser.ts new file mode 100644 index 0000000..593f216 --- /dev/null +++ b/src/lib/hooks/useUser.ts @@ -0,0 +1,17 @@ +import fetcher from '@lib/fetcher' +import { User } from '@my-types/user' +import useSWRImmutable from 'swr/immutable' + +export function useUser() { + const { data, isLoading } = useSWRImmutable<{ + message: User + success: boolean + }>(`/api/auth/user`, fetcher) + + let user: User | undefined = undefined + if (data && data.success) { + user = data.message + } + + return { user, isLoading } +} diff --git a/src/lib/kysely.ts b/src/lib/kysely.ts new file mode 100644 index 0000000..1335f30 --- /dev/null +++ b/src/lib/kysely.ts @@ -0,0 +1,30 @@ +import { DB } from '@my-types/generated/database' +import { createKysely } from '@vercel/postgres-kysely' +import { Kysely, PostgresDialect } from 'kysely' +import { Pool } from 'pg' + +const getDb = () => { + let db: Kysely | undefined = undefined + const { DATABASE_URL, NODE_ENV } = process.env + + if (NODE_ENV === 'production') { + db = createKysely({ + connectionString: DATABASE_URL, + ssl: true, + }) + } else { + db = new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + connectionString: DATABASE_URL, + }), + }), + }) + } + + return db +} + +const db = getDb() + +export default db diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a10923f..a13fe96 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,12 +2,21 @@ import type { AppProps } from 'next/app' import { MantineProvider } from '@mantine/core' import { Notifications } from '@mantine/notifications' import theme from '@lib/theme' +import DefaultLayout from '@layouts/DefaultLayout' +import { useUser } from '@lib/hooks/useUser' +import { UserContext } from '@contexts/UserContext' export default function App({ Component, pageProps }: AppProps) { - return ( - - - - - ) + const { user, isLoading } = useUser() + + return ( + + + + + + + + + ) } diff --git a/src/pages/api/add-crosshair.ts b/src/pages/api/add-crosshair.ts new file mode 100644 index 0000000..7bee703 --- /dev/null +++ b/src/pages/api/add-crosshair.ts @@ -0,0 +1,56 @@ +// POST - /api/add-crosshair +/* + body: { + name: string + crosshairCode: string + } +*/ + +import type { NextApiRequest, NextApiResponse } from 'next' +import { sessionOptions } from '@lib/auth/session' +import kysely from '@lib/kysely' +import { AddCrosshairFormValues } from '@components/AddCrosshairForm' +import { getIronSession } from 'iron-session' +import { User } from '@my-types/user' +import { PostAddCrosshairResponse } from '@my-types/api-responses/AddCrosshair' + +interface PostRequest extends NextApiRequest { + body: AddCrosshairFormValues +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + switch (req.method) { + case 'POST': { + return postAddCrosshair(req, res) + } + } +} + +async function postAddCrosshair( + req: PostRequest, + res: NextApiResponse +) { + try { + const sessionUser = await getIronSession(req, res, sessionOptions) + if (sessionUser.id <= 0) + throw new Error('You do not have access to this.') + const { crosshairCode, name } = req.body + + await kysely + .insertInto('crosshairs') + .values({ crosshair: crosshairCode, name, user_id: sessionUser.id }) + .execute() + + return res + .status(200) + .json({ message: 'Crosshair added successfully!', success: true }) + } catch (error: any) { + return res.json({ + message: new Error(error).message, + success: false, + }) + } +} diff --git a/src/pages/api/auth/authenticate.ts b/src/pages/api/auth/authenticate.ts new file mode 100644 index 0000000..8c4002c --- /dev/null +++ b/src/pages/api/auth/authenticate.ts @@ -0,0 +1,56 @@ +// /api/auth/authenticate + +import { NextApiRequest, NextApiResponse } from 'next' +import { getIronSession } from 'iron-session' +import { steamAuth } from '@lib/auth/steamAuth' +import { sessionOptions } from '@lib/auth/session' +import db from '@lib/kysely' +import { User } from '@my-types/user' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + const steamUser = await steamAuth.authenticate(req) + + const demodiveId = await retrieveOrCreateUser(steamUser.steamid) + const session = await getIronSession(req, res, sessionOptions) + + session.id = demodiveId + session.username = steamUser.username + session.avatarUrl = steamUser.avatar.large + + await session.save() + + return res.redirect('/manager') + } catch (error: any) { + return res.redirect(`/?loginError=true`) + } + } +} + +async function retrieveOrCreateUser(steamUserId: string) { + const existingUser = await db + .selectFrom('users') + .select('id') + .where('steam_uid', '=', steamUserId) + .executeTakeFirst() + + if (existingUser) { + return existingUser.id + } + + const newUser = await db + .insertInto('users') + .values({ steam_uid: steamUserId }) + .returning('id') + .executeTakeFirst() + + if (newUser) { + return newUser.id + } + + throw new Error('Error creating/retrieving user from database.') +} diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts new file mode 100644 index 0000000..fa9e4f7 --- /dev/null +++ b/src/pages/api/auth/login.ts @@ -0,0 +1,21 @@ +// /api/auth/login + +import { steamAuth } from '@lib/auth/steamAuth'; +import { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + const redirectUrl = await steamAuth.getRedirectUrl(); + return res.redirect(redirectUrl); + } catch (error: any) { + return res.json({ + message: new Error(error).message, + success: false, + }); + } + } +} diff --git a/src/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts new file mode 100644 index 0000000..87da45a --- /dev/null +++ b/src/pages/api/auth/logout.ts @@ -0,0 +1,23 @@ +// /api/auth/logout + +import { sessionOptions } from '@lib/auth/session' +import { getIronSession } from 'iron-session' +import { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === 'GET') { + try { + const session = await getIronSession(req, res, sessionOptions) + session.destroy() + return res.redirect('/') + } catch (error: any) { + return res.json({ + message: new Error(error).message, + success: false, + }) + } + } +} diff --git a/src/pages/api/auth/user.ts b/src/pages/api/auth/user.ts new file mode 100644 index 0000000..fd97d9b --- /dev/null +++ b/src/pages/api/auth/user.ts @@ -0,0 +1,59 @@ +// /api/auth/user + +import { sessionOptions } from '@lib/auth/session' +import db from '@lib/kysely' +import { User } from '@my-types/user' +import { getIronSession } from 'iron-session' +import { sql } from 'kysely' +import { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + switch (req.method) { + case 'GET': { + return getUser(req, res) + } + } +} + +async function getUser(req: NextApiRequest, res: NextApiResponse) { + try { + const sessionUser = await getIronSession(req, res, sessionOptions) + + if (!sessionUser) { + throw new Error('Session user does not exist.') + } + + const user = await db + .selectFrom('users') + .selectAll() + .where('id', '=', sessionUser.id) + .executeTakeFirstOrThrow() + + await db + .updateTable('users') + .set({ + latest_login_at: sql`NOW()`, + }) + .where('id', '=', sessionUser.id) + .execute() + + const currentUser: User = { + id: user.id, + username: sessionUser.username, + avatarUrl: sessionUser.avatarUrl, + } + + return res.status(200).json({ message: currentUser, success: true }) + } catch (error: any) { + const session = await getIronSession(req, res, sessionOptions) + session.destroy() + + return res.json({ + message: new Error(error).message, + success: false, + }) + } +} diff --git a/src/pages/api/crosshair.ts b/src/pages/api/crosshair.ts new file mode 100644 index 0000000..acced13 --- /dev/null +++ b/src/pages/api/crosshair.ts @@ -0,0 +1,115 @@ +// GET - /api/crosshair + +// POST - /api/crosshair +/* + body: { + crosshairs: DBTypes['crosshairs'][] + crosshairsToDelete: number[] + } +*/ + +import type { NextApiRequest, NextApiResponse } from 'next' +import { sessionOptions } from '@lib/auth/session' +import kysely from '@lib/kysely' +import { + GetCrosshairResponse, + PostCrosshairResponse, +} from '@my-types/api-responses/Crosshair' +import { getIronSession } from 'iron-session' +import { User } from '@my-types/user' +import { EditCrosshairFormValues } from '@components/EditCrosshairsForm' + +interface PostRequest extends NextApiRequest { + body: EditCrosshairFormValues +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + switch (req.method) { + case 'GET': { + return getCrosshairs(req, res) + } + + case 'POST': { + return postCrosshairs(req, res) + } + } +} + +async function getCrosshairs( + req: NextApiRequest, + res: NextApiResponse +) { + try { + const sessionUser = await getIronSession(req, res, sessionOptions) + if (sessionUser.id <= 0) + throw new Error('You do not have access to this.') + + const crosshairs = await kysely + .selectFrom('crosshairs') + .selectAll() + .where('crosshairs.user_id', '=', sessionUser.id) + .execute() + + return res.status(200).json({ message: crosshairs, success: true }) + } catch (error: any) { + return res.json({ + message: new Error(error).message, + success: false, + }) + } +} + +async function postCrosshairs( + req: PostRequest, + res: NextApiResponse +) { + try { + const sessionUser = await getIronSession(req, res, sessionOptions) + if (sessionUser.id <= 0) + throw new Error('You do not have access to this.') + const { crosshairs, crosshairsToDelete } = req.body + + if (crosshairs.length <= 0 && crosshairsToDelete.length <= 0) + throw new Error('There is nothing to update.') + + let response: string[] = [] + + if (crosshairsToDelete.length > 0) { + await kysely + .deleteFrom('crosshairs') + .where('id', 'in', crosshairsToDelete) + .execute() + + response.push( + `Successfully deleted ${crosshairsToDelete.length} crosshair(s)` + ) + } + + if (crosshairs.length > 0) { + await kysely + .insertInto('crosshairs') + .values(crosshairs) + .onConflict((oc) => + oc.column('id').doUpdateSet((eb) => ({ + name: eb.ref('excluded.name'), + crosshair: eb.ref('excluded.crosshair'), + })) + ) + .execute() + + response.push(`Upserted ${crosshairs.length} crosshair(s)`) + } + + return res + .status(200) + .json({ message: response.join(', '), success: true }) + } catch (error: any) { + return res.json({ + message: new Error(error).message, + success: false, + }) + } +} diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts deleted file mode 100644 index f8bcc7e..0000000 --- a/src/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next' - -type Data = { - name: string -} - -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f46c5bd..20600df 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,24 +1,15 @@ import { CrosshairConverter } from '@components/CrosshairConverter' -import { CrosshairPreview } from '@components/CrosshairPreview' -import { Center, Group, Stack, Title } from '@mantine/core' - -// const CROSSHAIR_CODES = [ -// 'CSGO-SBTJQ-Mv4Wj-RrEd9-hu2hX-PwUvD', // Thick T, huge gap -// 'CSGO-78PT7-mHExG-UGt7C-HAhdB-GxuWK', // Cai crosshair -// 'CSGO-obHU6-NLWyP-EtkUh-pywKV-TXQ3A', //yellow dot -// 'CSGO-9r8iB-9WwzR-tndTZ-oQo6P-nysyM', // bluesmall -// 'CSGO-Vk7Cz-XXVk2-ZcZZu-F7CoJ-AzZkF', // rhys -// ] +import { Stack, Text } from '@mantine/core' export default function Home() { return ( -
- - - CS2 Crosshair Converter - - - -
+ + + Enter in your crosshair code below, and hit the button to + receive the commands needed to enter in game to set that + crosshair. + + + ) } diff --git a/src/pages/manager.tsx b/src/pages/manager.tsx new file mode 100644 index 0000000..ca752a7 --- /dev/null +++ b/src/pages/manager.tsx @@ -0,0 +1,80 @@ +import { CrosshairPreview } from '@components/CrosshairPreview' +import { UserCrosshairs } from '@components/UserCrosshairs' +import { UserContext } from '@contexts/UserContext' +import { Text, Stack, Flex, Loader, Center } from '@mantine/core' +import { useContext } from 'react' + +const SHOOBIE_CROSSHAIR_CODES = [ + { crosshairCode: 'CSGO-obHU6-NLWyP-EtkUh-pywKV-TXQ3A', name: 'Yellow Dot' }, + { + crosshairCode: 'CSGO-78PT7-mHExG-UGt7C-HAhdB-GxuWK', + name: 'Cai DM clip', + }, + { crosshairCode: 'CSGO-RoxEW-xZqYv-nzUQZ-UVtvi-cV6uC', name: 'HEAP' }, + { crosshairCode: 'CSGO-9r8iB-9WwzR-tndTZ-oQo6P-nysyM', name: 'Blue Small' }, + { crosshairCode: 'CSGO-5fKKR-Eab4o-viAxH-Xvx38-T3SKF', name: 'Norwi' }, + { crosshairCode: 'CSGO-EiXND-5jUGt-Ru2cm-LwVKc-u6GSQ', name: 'Ryan' }, + { + crosshairCode: 'CSGO-QeeOU-PKiYd-VH3Rz-5EtTr-8t8rN', + name: 'Vertigo Pugger', + }, +] + +export default function Manager() { + const { user, isLoading } = useContext(UserContext) + + return ( + + {isLoading && ( +
+ +
+ )} + + {!isLoading && !user && ( + <> + + + Login to be able to manage crosshairs. In the + meantime, here are a choice of crosshairs from + shoobie! + + + Click on a card below to copy the console commands + for it. + + + + {SHOOBIE_CROSSHAIR_CODES.map((c, i) => ( + + ))} + + + )} + + {!isLoading && user && ( + <> + + + Welcome, {user.username}! + + + Add crosshairs, and then click on a card below to + copy the console commands for it. + + + + + )} +
+ ) +} diff --git a/src/types/api-responses/AddCrosshair.ts b/src/types/api-responses/AddCrosshair.ts new file mode 100644 index 0000000..9be9f59 --- /dev/null +++ b/src/types/api-responses/AddCrosshair.ts @@ -0,0 +1,4 @@ +export type PostAddCrosshairResponse = { + message: string + success: boolean +} diff --git a/src/types/api-responses/Crosshair.ts b/src/types/api-responses/Crosshair.ts new file mode 100644 index 0000000..3cf8e08 --- /dev/null +++ b/src/types/api-responses/Crosshair.ts @@ -0,0 +1,16 @@ +import { DBTypes } from '@my-types/database' + +export type GetCrosshairResponse = + | { + message: DBTypes['crosshairs'][] + success: true + } + | { + message: string + success: false + } + +export type PostCrosshairResponse = { + message: string + success: boolean +} diff --git a/src/types/crosshair.ts b/src/types/crosshair.ts new file mode 100644 index 0000000..888e584 --- /dev/null +++ b/src/types/crosshair.ts @@ -0,0 +1,39 @@ +// NOTE: Once import is properly fixed from csgo-sharecode, will no longer need this definition here + +export interface Crosshair { + length: number + red: number + green: number + blue: number + gap: number + alphaEnabled: boolean + alpha: number + outlineEnabled: boolean + outline: number + color: number + thickness: number + centerDotEnabled: boolean + splitDistance: number + followRecoil: boolean + fixedCrosshairGap: number + innerSplitAlpha: number + outerSplitAlpha: number + splitSizeRatio: number + tStyleEnabled: boolean + deployedWeaponGapEnabled: boolean + /** + * CS:GO + * 0 => Default + * 1 => Default static + * 2 => Classic + * 3 => Classic dynamic + * 4 => Classic static + */ + /** + * CS2 + * 0 to 3 => Classic + * 4 => Classic static + * 5 => Legacy + */ + style: number +} diff --git a/src/types/database.ts b/src/types/database.ts new file mode 100644 index 0000000..8c34ca6 --- /dev/null +++ b/src/types/database.ts @@ -0,0 +1,6 @@ +import type { Selectable } from 'kysely'; +import { DB } from './generated/database'; + +export type DBTypes = { + [K in keyof DB]: Selectable; +}; diff --git a/src/types/generated/database.ts b/src/types/generated/database.ts new file mode 100644 index 0000000..f2df928 --- /dev/null +++ b/src/types/generated/database.ts @@ -0,0 +1,27 @@ +import type { ColumnType } from "kysely"; + +export type Generated = T extends ColumnType + ? ColumnType + : ColumnType; + +export type Timestamp = ColumnType; + +export interface Crosshairs { + created_at: Generated; + crosshair: string; + id: Generated; + name: string; + user_id: number | null; +} + +export interface Users { + created_at: Generated; + id: Generated; + latest_login_at: Timestamp | null; + steam_uid: string; +} + +export interface DB { + crosshairs: Crosshairs; + users: Users; +} diff --git a/src/types/user.ts b/src/types/user.ts new file mode 100644 index 0000000..1715afc --- /dev/null +++ b/src/types/user.ts @@ -0,0 +1,5 @@ +export interface User { + id: number + username: string + avatarUrl: string +} diff --git a/tsconfig-database.json b/tsconfig-database.json new file mode 100644 index 0000000..7d00d90 --- /dev/null +++ b/tsconfig-database.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2019", + "outDir": "distDatabase", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true + }, + "include": ["./database/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 3a3cce4..7071960 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "incremental": true, "typeRoots": ["./src/typings"], "paths": { + "@my-types/*": ["./src/types/*"], "@*": ["./src/*"] } },