feat: stuffs

This commit is contained in:
diced 2023-06-27 22:39:39 -07:00
parent 8775ae2e29
commit e4761c3f12
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
49 changed files with 1302 additions and 866 deletions

View file

@ -1,11 +1,6 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'@remix-run/eslint-config',
'@remix-run/eslint-config/node',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/recommended',
],
extends: ['next/core-web-vitals', 'plugin:prettier/recommended', 'plugin:@typescript-eslint/recommended'],
root: true,
plugins: ['unused-imports', '@typescript-eslint'],
parser: '@typescript-eslint/parser',

33
.gitignore vendored
View file

@ -1,18 +1,28 @@
node_modules
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/.cache
/build
/public/build
# dependencies
/node_modules
/.pnp
.pnp.js
# yarn
.yarn/*
!.yarn/releases
!.yarn/plugins
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
.idea
# debug
npm-debug.log*
@ -20,15 +30,14 @@ yarn-debug.log*
yarn-error.log*
# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# zipline
uploads*/
dist/
uploads*/

6
next.config.js Normal file
View file

@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig

View file

@ -6,13 +6,12 @@
"version": "4.0.0-dev.1",
"scripts": {
"build": "run-s build:*",
"build:remix": "remix build",
"build:remix": "next build",
"build:server": "tsup",
"dev": "run-p dev:remix & (run-s dev:build && run-s dev:server)",
"dev": "run-s dev:build dev:server",
"dev:build": "cross-env NODE_ENV=development run-s build:server",
"dev:remix": "cross-env NODE_ENV=development remix watch",
"dev:server": "cross-env NODE_ENV=development DEBUG=zipline node --require ./node_modules/dotenv/config ./build/server.js",
"start": "node ./server.mjs",
"start": "node ./build/server.mjs",
"lint": "eslint --cache --ignore-path .gitignore --fix .",
"format": "prettier --write --ignore-path .gitignore .",
"validate": "run-p lint format"
@ -26,23 +25,20 @@
"@mantine/form": "^6.0.14",
"@mantine/hooks": "^6.0.14",
"@mantine/modals": "^6.0.14",
"@mantine/next": "^6.0.14",
"@mantine/notifications": "^6.0.14",
"@mantine/prism": "^6.0.14",
"@mantine/remix": "^6.0.14",
"@mantine/nprogress": "^6.0.14",
"@prisma/client": "4.16.1",
"@prisma/internals": "^4.16.1",
"@prisma/migrate": "^4.16.1",
"@remix-run/express": "^1.17.1",
"@remix-run/node": "^1.16.1",
"@remix-run/react": "^1.16.1",
"@remix-run/v1-route-convention": "^0.1.2",
"@types/express": "^4.17.17",
"@tabler/icons-react": "^2.22.0",
"argon2": "^0.30.3",
"bytes": "^3.1.2",
"colorette": "^2.0.20",
"dayjs": "^1.11.8",
"express": "^4.18.2",
"isbot": "^3.6.10",
"ms": "^2.1.3",
"next": "^13.4.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"znv": "^0.3.2",
@ -52,6 +48,7 @@
"@remix-run/dev": "^1.16.1",
"@remix-run/eslint-config": "^1.16.1",
"@types/bytes": "^3.1.1",
"@types/express": "^4.17.17",
"@types/node": "^20.3.1",
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",

14
postcss.config.js Normal file
View file

@ -0,0 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

View file

@ -1,234 +0,0 @@
-- CreateEnum
CREATE TYPE "OAuthProviderType" AS ENUM ('DISCORD', 'GOOGLE', 'GITHUB');
-- CreateEnum
CREATE TYPE "LimitType" AS ENUM ('UPLOAD_COUNT', 'UPLOAD_SIZE', 'SHORTEN_COUNT');
-- CreateEnum
CREATE TYPE "LimitTimeframe" AS ENUM ('SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY');
-- CreateEnum
CREATE TYPE "IncompleteFileStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETE', 'FAILED');
-- CreateTable
CREATE TABLE "zipline_meta" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"firstSetup" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "zipline_meta_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT,
"avatar" TEXT,
"token" TEXT NOT NULL,
"administrator" BOOLEAN NOT NULL DEFAULT false,
"ziplineId" TEXT NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "OAuthProvider" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
"provider" "OAuthProviderType" NOT NULL,
"accessToken" TEXT NOT NULL,
"refreshToken" TEXT NOT NULL,
"expiresIn" INTEGER NOT NULL,
"scope" TEXT NOT NULL,
"tokenType" TEXT NOT NULL,
"profile" JSONB NOT NULL,
CONSTRAINT "OAuthProvider_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserLimit" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"type" "LimitType" NOT NULL,
"value" INTEGER NOT NULL,
"timeframe" "LimitTimeframe" NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "UserLimit_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "File" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletesAt" TIMESTAMP(3),
"name" TEXT NOT NULL,
"originalName" TEXT NOT NULL,
"path" TEXT NOT NULL,
"size" INTEGER NOT NULL,
"type" TEXT NOT NULL,
"views" INTEGER NOT NULL DEFAULT 0,
"favorite" BOOLEAN NOT NULL DEFAULT false,
"password" TEXT,
"zeroWidthSpace" TEXT,
"userId" TEXT,
"folderId" TEXT,
CONSTRAINT "File_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Folder" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "Folder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "IncompleteFile" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"status" "IncompleteFileStatus" NOT NULL,
"chunksTotal" INTEGER NOT NULL,
"chunksComplete" INTEGER NOT NULL,
"metadata" JSONB NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "IncompleteFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Url" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"vanity" TEXT,
"destination" TEXT NOT NULL,
"name" TEXT NOT NULL,
"zeroWidthSpace" TEXT,
"userId" TEXT,
CONSTRAINT "Url_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Metric" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"data" JSONB NOT NULL,
"ziplineId" TEXT NOT NULL,
CONSTRAINT "Metric_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Invite" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"expiresAt" TIMESTAMP(3),
"code" TEXT NOT NULL,
"used" BOOLEAN NOT NULL DEFAULT false,
"inviterId" TEXT NOT NULL,
"ziplineId" TEXT NOT NULL,
CONSTRAINT "Invite_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_FileToTag" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "User_token_key" ON "User"("token");
-- CreateIndex
CREATE UNIQUE INDEX "OAuthProvider_userId_provider_key" ON "OAuthProvider"("userId", "provider");
-- CreateIndex
CREATE UNIQUE INDEX "UserLimit_type_key" ON "UserLimit"("type");
-- CreateIndex
CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Url_name_key" ON "Url"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Invite_code_key" ON "Invite"("code");
-- CreateIndex
CREATE UNIQUE INDEX "_FileToTag_AB_unique" ON "_FileToTag"("A", "B");
-- CreateIndex
CREATE INDEX "_FileToTag_B_index" ON "_FileToTag"("B");
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OAuthProvider" ADD CONSTRAINT "OAuthProvider_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserLimit" ADD CONSTRAINT "UserLimit_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "File" ADD CONSTRAINT "File_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IncompleteFile" ADD CONSTRAINT "IncompleteFile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Url" ADD CONSTRAINT "Url_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Metric" ADD CONSTRAINT "Metric_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_inviterId_fkey" FOREIGN KEY ("inviterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View file

@ -13,12 +13,6 @@ model Zipline {
updatedAt DateTime @updatedAt
firstSetup Boolean @default(true)
metrics Metric[]
users User[]
invite Invite[]
@@map("zipline_meta")
}
model User {
@ -39,9 +33,6 @@ model User {
invites Invite[]
oauthProviders OAuthProvider[]
IncompleteFile IncompleteFile[]
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
ziplineId String
}
model OAuthProvider {
@ -191,9 +182,6 @@ model Metric {
updatedAt DateTime @updatedAt
data Json
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
ziplineId String
}
model Invite {
@ -207,7 +195,4 @@ model Invite {
inviter User @relation(fields: [inviterId], references: [id], onDelete: Cascade, onUpdate: Cascade)
inviterId String
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
ziplineId String
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,27 +0,0 @@
const { createRoutesFromFolders } = require('@remix-run/v1-route-convention');
/**
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = {
ignoredRouteFiles: ['**/.*'],
appDirectory: 'src/app',
// assetsBuildDirectory: 'public/build',
// serverBuildPath: 'build/index.js',
serverModuleFormat: 'cjs',
future: {
unstable_dev: true,
v2_routeConvention: true,
v2_errorBoundary: true,
v2_meta: true,
v2_normalizeFormMethod: true,
v2_headers: true,
},
publicPath: '/modules/',
// use directory structure.
routes(defineRoutes) {
return createRoutesFromFolders(defineRoutes, {
appDirectory: 'src/app',
});
},
};

2
remix.env.d.ts vendored
View file

@ -1,2 +0,0 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

View file

@ -1,11 +0,0 @@
import { RemixBrowser } from '@remix-run/react';
import { hydrate } from 'react-dom';
import { ClientProvider } from '@mantine/remix';
hydrate(
<ClientProvider>
<RemixBrowser />
</ClientProvider>,
document
);

View file

@ -1,21 +0,0 @@
import { renderToString } from 'react-dom/server';
import { RemixServer } from '@remix-run/react';
import type { EntryContext } from '@remix-run/node';
import { injectStyles, createStylesServer } from '@mantine/remix';
const server = createStylesServer();
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(<RemixServer context={remixContext} url={request.url} />);
responseHeaders.set('Content-Type', 'text/html');
return new Response(`<!DOCTYPE html>${injectStyles(markup, server)}`, {
status: responseStatusCode,
headers: responseHeaders,
});
}

View file

@ -1,10 +0,0 @@
import type { LoaderArgs } from '@remix-run/node';
import type { Config } from 'lib/config/Config';
export type TypedLoaderArgs<T> = {
context: T;
} & Omit<LoaderArgs, 'context'>;
export type RouteContext = {
config: Config;
};

View file

@ -1,32 +0,0 @@
import type { V2_MetaFunction } from '@remix-run/node';
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
import { MantineProvider, createEmotionCache } from '@mantine/core';
import { StylesPlaceholder } from '@mantine/remix';
export const meta: V2_MetaFunction = () => [
{ charSet: 'utf-8' },
{ title: 'Zipline' },
{ name: 'viewport', content: 'width=device-width,initial-scale=1' },
];
createEmotionCache({ key: 'mantine' });
export default function App() {
return (
<MantineProvider withGlobalStyles withNormalizeCSS>
<html lang='en'>
<head>
<StylesPlaceholder />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
</MantineProvider>
);
}

View file

@ -1,35 +0,0 @@
import { json } from '@remix-run/node';
import { isRouteErrorResponse, useRouteError } from '@remix-run/react';
import { RouteContext, TypedLoaderArgs } from '~/loader';
export const loader = async ({ params, context }: TypedLoaderArgs<RouteContext>) => {
const slug = params['*']?.split('/').filter(Boolean) ?? [];
if (!slug.length) {
throw json('Not Found (no slug)', { status: 404 });
}
console.log(slug, context.config);
if (slug[0] === context.config.files.route) {
} else {
throw json('Not Found (catchall)', { status: 404 });
}
return json({ slug });
};
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <pre>{JSON.stringify(error, null, 2)}</pre>;
} else if (error instanceof Error) {
return <pre>{JSON.stringify(error, null, 2)}</pre>;
} else {
return <p>Unknown error</p>;
}
}
// export default function Index() {
// return <p>Index</p>;
// }

View file

@ -1,13 +0,0 @@
import { LoaderArgs, json } from '@remix-run/node';
import { prisma } from '~/db.server';
export async function loader({ context, request }: LoaderArgs) {
try {
// test database connection
await prisma.user.count();
return json({ pong: true }, { status: 200 });
} catch (e) {
return json({ pong: false }, { status: 500 });
}
}

View file

@ -1,22 +0,0 @@
import { LoaderArgs, json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { prisma } from '~/db.server';
export async function loader({}: LoaderArgs) {
let zipline = await prisma.zipline.findFirst();
if (!zipline) {
zipline = await prisma.zipline.create({ data: {} });
}
return json({ zipline });
}
export default function Index() {
const { zipline } = useLoaderData<typeof loader>();
return (
<div>
<pre>{JSON.stringify(zipline, null, 2)}</pre>
</div>
);
}

View file

@ -1,25 +0,0 @@
import { createCookieSessionStorage } from "@remix-run/node";
let sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret) {
throw new Error("SESSION_SECRET must be set");
}
export let sessionStorage = createCookieSessionStorage({
cookie: {
name: "__session",
secrets: [sessionSecret],
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
},
});
const USER_SESSION_KEY = "userId";
export async function getSession(request: Request) {
const cookie = request.headers.get("Cookie");
return sessionStorage.getSession(cookie);
}

View file

@ -1,3 +0,0 @@
export function sleep<T>(ms: number, value: T) {
return new Promise<T>((resolve) => setTimeout(() => resolve(value), ms));
}

View file

@ -1,14 +0,0 @@
export interface Config {
core: ConfigCore;
files: ConfigFiles;
}
export interface ConfigCore {
port: number;
sessionSecret: string;
databaseUrl: string;
}
export interface ConfigFiles {
route: string;
}

View file

@ -1,14 +0,0 @@
import type { ValidatedEnv } from './read';
export function convertEnv(env: ValidatedEnv) {
return {
core: {
port: env.PORT,
sessionSecret: env.SESSION_SECRET,
databaseUrl: env.DATABASE_URL,
},
files: {
route: env.FILES_ROUTE,
},
};
}

16
src/lib/config/index.ts Normal file
View file

@ -0,0 +1,16 @@
import { readEnv } from './read';
import { validateEnv, Config } from './validate';
let config: Config;
declare global {
var __config__: Config;
}
if (!global.__config__) {
global.__config__ = validateEnv(readEnv());
}
config = global.__config__;
export { config };

View file

@ -1,23 +1,120 @@
import { log } from 'src/lib/logger';
import { parseEnv } from 'znv';
import { z } from 'zod';
import bytes from 'bytes';
import msFn from 'ms';
const logger = log('config').c('read');
type EnvType = 'string' | 'number' | 'boolean' | 'byte' | 'ms';
export type ParsedEnv = ReturnType<typeof readEnv>;
export const PROP_TO_ENV: Record<string, string> = {
'core.port': 'CORE_PORT',
'core.hostname': 'CORE_HOSTNAME',
'core.secret': 'CORE_SECRET',
'core.databaseUrl': 'CORE_DATABASE_URL',
'files.route': 'FILES_ROUTE',
};
export function readEnv() {
logger.debug('reading env');
const envs = [
env(PROP_TO_ENV['core.port'], 'core.port', 'number'),
env(PROP_TO_ENV['core.hostname'], 'core.hostname', 'string'),
env(PROP_TO_ENV['core.secret'], 'core.secret', 'string'),
env(PROP_TO_ENV['core.databaseUrl'], 'core.databaseUrl', 'string'),
const validation = parseEnv(process.env, {
PORT: z.number().default(3000),
SESSION_SECRET: z.string(),
DATABASE_URL: z.string(),
env(PROP_TO_ENV['files.route'], 'files.route', 'string'),
];
FILES_ROUTE: z.string().default('u'),
});
const raw = {
core: {
port: undefined,
hostname: undefined,
secret: undefined,
databaseUrl: undefined,
},
files: {
route: undefined,
},
};
logger.debug('env read', JSON.stringify(validation));
for (let i = 0; i !== envs.length; ++i) {
const env = envs[i];
const value = process.env[env.variable];
if (value === undefined) continue;
return validation;
const parsed = parse(value, env.type);
if (parsed === undefined) continue;
setDotProp(raw, env.property, parsed);
}
return raw;
}
export type ValidatedEnv = ReturnType<typeof readEnv>;
function env(variable: string, property: string, type: EnvType) {
return {
variable,
property,
type,
};
}
function setDotProp(obj: Record<string, any>, property: string, value: unknown) {
const parts = property.split('.');
const last = parts.pop()!;
for (let i = 0; i !== parts.length; ++i) {
const part = parts[i];
const next = obj[part];
if (typeof next === 'object' && next !== null) {
obj = next;
} else {
obj = obj[part] = {};
}
}
obj[last] = value;
}
function parse(value: string, type: EnvType) {
switch (type) {
case 'string':
return string(value);
case 'number':
return number(value);
case 'boolean':
return boolean(value);
case 'byte':
return byte(value);
case 'ms':
return ms(value);
default:
return undefined;
}
}
function string(value: string) {
return value;
}
function number(value: string) {
const num = Number(value);
if (isNaN(num)) return undefined;
return num;
}
function boolean(value: string) {
if (value === 'true') return true;
if (value === 'false') return false;
return undefined;
}
function byte(value: string) {
return bytes(value);
}
function ms(value: string) {
return msFn(value);
}

View file

@ -0,0 +1,68 @@
import { ZodError, z } from 'zod';
import { PROP_TO_ENV, ParsedEnv } from './read';
import { log } from '../logger';
const schema = z.object({
core: z.object({
port: z.number().default(3000),
hostname: z.string().default('localhost'),
secret: z.string().superRefine((s, c) => {
if (s === 'changethis')
return c.addIssue({
code: 'custom',
message: 'Secret must be changed from the default value',
path: ['core', 'secret'],
});
if (s.length <= 16) {
return c.addIssue({
code: 'too_small',
minimum: 16,
type: 'string',
inclusive: true,
message: 'Secret must contain at least 16 characters',
path: ['core', 'secret'],
exact: false,
});
}
}),
databaseUrl: z.string().url(),
}),
files: z.object({
route: z.string().default('u'),
}),
});
export type Config = z.infer<typeof schema>;
export function validateEnv(env: ParsedEnv): Config {
const logger = log('config').c('validate');
try {
const validated = schema.parse(env);
if (!validated) {
logger.error('There was an error while validating the environment.');
process.exit(1);
}
return validated;
} catch (e) {
if (e instanceof ZodError) {
logger.error(`There were ${e.errors.length} error(s) while validating the environment.`);
for (let i = 0; i !== e.errors.length; ++i) {
const error = e.errors[i];
logger.debug(JSON.stringify(error));
const path = PROP_TO_ENV[error.path.join('.')];
logger.error(`${path}: ${error.message}`);
}
process.exit(1);
}
throw e;
}
}

19
src/lib/cookie.ts Normal file
View file

@ -0,0 +1,19 @@
export function serializeCookie(
name: string,
value: string,
options: {
expires?: Date;
maxAge?: number;
path?: string;
sameSite?: 'strict' | 'lax' | 'none';
} = {}
) {
const cookie = [`${name}=${value}`];
if (options.expires) cookie.push(`Expires=${options.expires.toUTCString()}`);
if (options.maxAge) cookie.push(`Max-Age=${options.maxAge}`);
if (options.path) cookie.push(`Path=${options.path}`);
if (options.sameSite) cookie.push(`SameSite=${options.sameSite}`);
return cookie.join('; ');
}

97
src/lib/crypto.ts Normal file
View file

@ -0,0 +1,97 @@
import crypto from 'crypto';
import { hash, verify } from 'argon2';
const ALGORITHM = 'aes-256-cbc';
export function createKey(secret: string) {
const hash = crypto.createHash('sha256');
hash.update(secret);
return hash.digest('hex').slice(0, 32);
}
export function encrypt(value: string, secret: string): string {
const key = createKey(secret);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key), iv);
const encrypted = cipher.update(value);
const final = cipher.final();
const buffer = Buffer.alloc(encrypted.length + final.length);
buffer.set(encrypted);
buffer.set(final, encrypted.length);
return iv.toString('hex') + '.' + buffer.toString('hex');
}
export function decrypt(value: string, secret: string): string {
const key = createKey(secret);
const [iv, encrypted] = value.split('.');
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key), Buffer.from(iv, 'hex'));
const decrypted = decipher.update(Buffer.from(encrypted, 'hex'));
const final = decipher.final();
const buffer = Buffer.alloc(decrypted.length + final.length);
buffer.set(decrypted);
buffer.set(final, decrypted.length);
return buffer.toString();
}
export function randomCharacters(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let result = '';
for (let i = 0; i !== length; ++i) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
export function createToken(): string {
const date = Date.now();
const random = randomCharacters(32);
const date64 = Buffer.from(date.toString()).toString('base64');
const random64 = Buffer.from(random).toString('base64');
return `${date64}.${random64}`;
}
export function encryptToken(token: string, secret: string): string {
const key = createKey(secret);
const date = Date.now();
const date64 = Buffer.from(date.toString()).toString('base64');
const encrypted = encrypt(token, key);
const encrypted64 = Buffer.from(encrypted).toString('base64');
return `${date64}.${encrypted64}`;
}
export function decryptToken(encryptedToken: string, secret: string): [number, string] {
const key = createKey(secret);
const [date64, encrypted64] = encryptedToken.split('.');
const date = parseInt(Buffer.from(date64, 'base64').toString('ascii'), 10);
const encrypted = Buffer.from(encrypted64, 'base64').toString('ascii');
return [date, decrypt(encrypted, key)];
}
export async function hashPassword(password: string) {
return hash(password);
}
export async function verifyPassword(password: string, hash: string) {
return verify(hash, password);
}

View file

@ -1,5 +1,5 @@
import { PrismaClient } from '@prisma/client';
import { log } from 'src/lib/logger';
import { log } from '@/lib/logger';
let prisma: PrismaClient;

View file

@ -1,6 +1,6 @@
import { Migrate } from '@prisma/migrate/dist/Migrate';
import { ensureDatabaseExists } from '@prisma/migrate/dist/utils/ensureDatabaseExists';
import { log } from 'lib/logger';
import { log } from '@/lib/logger';
export async function runMigrations() {
const migrate = new Migrate('./prisma/schema.prisma');

View file

@ -0,0 +1,45 @@
import { Prisma } from '@prisma/client';
import { prisma } from '..';
export type User = {
id: string;
username: string;
createdAt: Date;
updatedAt: Date;
administrator: boolean;
avatar?: string | null;
password?: string | null;
};
export async function getUser(
where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput,
options?: { password?: boolean; avatar?: boolean }
): Promise<User | null> {
return prisma.user.findFirst({
where,
select: {
administrator: true,
avatar: options?.avatar || false,
id: true,
createdAt: true,
updatedAt: true,
password: options?.password || false,
username: true,
},
});
}
export async function getUserTokenRaw(
where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput
): Promise<string | null> {
const user = await prisma.user.findFirst({
where,
select: {
token: true,
},
});
if (!user) return null;
return user.token;
}

View file

@ -0,0 +1,12 @@
import { prisma } from '..';
export async function getZipline() {
const zipline = await prisma.zipline.findFirst();
if (!zipline) {
return prisma.zipline.create({
data: {},
});
}
return zipline;
}

View file

@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import { blue, green, red, yellow, gray, white, bold } from 'colorette';
import { green, red, yellow, gray, white, bold } from 'colorette';
export type LoggerLevel = 'info' | 'warn' | 'error' | 'debug' | 'trace';

View file

@ -0,0 +1,10 @@
import { NextApiReq, NextApiRes } from '../response';
export function combine(middleware: Middleware[], handler: Handler) {
return middleware.reduceRight((handler, middleware) => {
return middleware(handler);
}, handler);
}
export type Middleware = (...args: any[]) => Handler;
export type Handler = (req: NextApiReq, res: NextApiRes) => Promise<any>;

View file

@ -0,0 +1,19 @@
import { NextApiReq, NextApiRes } from '../response';
import { Handler } from './combine';
export function cors() {
return (handler: Handler) => {
return async (req: NextApiReq, res: NextApiRes) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept');
res.setHeader('Access-Control-Max-Age', '86400');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
return handler(req, res);
};
};
}

View file

@ -0,0 +1,19 @@
import { NextApiReq, NextApiRes, methodNotAllowed } from '../response';
import { Handler } from './combine';
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
export function method(allowedMethods: HttpMethod[] = []) {
return (handler: Handler) => {
return async (req: NextApiReq, res: NextApiRes) => {
allowedMethods.push('OPTIONS');
if (!allowedMethods.includes(req.method as HttpMethod)) {
res.setHeader('Allow', allowedMethods.join(', '));
return methodNotAllowed(res);
}
return handler(req, res);
};
};
}

View file

@ -0,0 +1,40 @@
import { config } from '../config';
import { decryptToken } from '../crypto';
import { prisma } from '../db';
import { NextApiReq, NextApiRes, forbidden, unauthorized } from '../response';
import { Handler } from './combine';
export type ZiplineAuthOptions = {
administratorOnly?: boolean;
};
export function ziplineAuth(options: ZiplineAuthOptions) {
return (handler: Handler) => {
return async (req: NextApiReq, res: NextApiRes) => {
let rawToken: string | undefined;
if (req.cookies.zipline_auth) rawToken = req.cookies.zipline_auth;
else if (req.headers.authorization) rawToken = req.headers.authorization;
if (!rawToken) return unauthorized(res);
const [date, token] = decryptToken(rawToken, config.core.secret);
if (isNaN(new Date(date).getTime())) return unauthorized(res);
const user = await prisma.user.findUnique({
where: {
token,
},
});
if (!user) return unauthorized(res);
req.user = user;
if (options.administratorOnly && !user.administrator) return forbidden(res);
return handler(req, res);
};
};
}

96
src/lib/response.ts Normal file
View file

@ -0,0 +1,96 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { User } from './db/queries/user';
export interface NextApiReq<Body = any, Query = any, Headers = any> extends NextApiRequest {
query: Query & { [k: string]: string | string[] };
body: Body;
headers: Headers & { [k: string]: string };
user?: User;
}
export type NextApiRes<Data = any> = NextApiResponse<Data>;
export function ok(res: NextApiRes, data: Record<string, unknown> = {}) {
return res.status(200).json(data);
}
// Client wrong data, etc
export function badRequest(
res: NextApiRes,
message: string = 'Bad Request',
data: Record<string, unknown> = {}
) {
return res.status(400).json({
error: message,
...data,
});
}
// No authorization
export function unauthorized(
res: NextApiRes,
message: string = 'Unauthorized',
data: Record<string, unknown> = {}
) {
return res.status(401).json({
error: message,
...data,
});
}
// User's permission level does not meet requirements for this resource
export function forbidden(
res: NextApiRes,
message: string = 'Forbidden',
data: Record<string, unknown> = {}
) {
return res.status(403).json({
error: message,
...data,
});
}
export function notFound(res: NextApiRes, message: string = 'Not Found', data: Record<string, unknown> = {}) {
return res.status(404).json({
error: message,
...data,
});
}
export function ratelimited(
res: NextApiRes,
retryAfter: number,
message: string = 'Ratelimited',
data: Record<string, unknown> = {}
) {
res.setHeader('Retry-After', retryAfter);
return res.status(429).json({
error: message,
retryAfter,
...data,
});
}
export function serverError(
res: NextApiRes,
message: string = 'Internal Server Error',
data: Record<string, unknown> = {}
) {
return res.status(500).json({
error: message,
...data,
});
}
export function methodNotAllowed(
res: NextApiRes,
message: string = 'Method Not Allowed',
data: Record<string, unknown> = {}
) {
return res.status(405).json({
error: message,
method: res.req?.method || 'unknown',
...data,
});
}

26
src/pages/_app.tsx Normal file
View file

@ -0,0 +1,26 @@
import { AppProps } from 'next/app';
import Head from 'next/head';
import { MantineProvider } from '@mantine/core';
export default function App(props: AppProps) {
const { Component, pageProps } = props;
return (
<>
<Head>
<title>Zipline</title>
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width' />
</Head>
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
colorScheme: 'dark',
}}
>
<Component {...pageProps} />
</MantineProvider>
</>
);
}

20
src/pages/_document.tsx Normal file
View file

@ -0,0 +1,20 @@
import { createGetInitialProps } from '@mantine/next';
import Document, { Head, Html, Main, NextScript } from 'next/document';
const getInitialProps = createGetInitialProps();
export default class _Document extends Document {
static getInitialProps = getInitialProps;
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

View file

@ -0,0 +1,56 @@
import { config } from '@/lib/config';
import { serializeCookie } from '@/lib/cookie';
import { encryptToken, verifyPassword } from '@/lib/crypto';
import { User, getUser, getUserTokenRaw } from '@/lib/db/queries/user';
import { combine } from '@/lib/middleware/combine';
import { cors } from '@/lib/middleware/cors';
import { method } from '@/lib/middleware/method';
import { NextApiReq, NextApiRes, badRequest, ok } from '@/lib/response';
type Data = {
user: User;
token: string;
};
type Body = {
username: string;
password: string;
}
async function handler(req: NextApiReq<Body>, res: NextApiRes<Data>) {
const { username, password } = req.body;
if (!username) return badRequest(res, 'Username is required');
if (!password) return badRequest(res, 'Password is required');
const user = await getUser({ username }, { password: true });
if (!user) return badRequest(res, 'Invalid username');
if (!user.password) return badRequest(res, 'User does not have a password, login through a provider');
const valid = await verifyPassword(password, user.password);
if (!valid) return badRequest(res, 'Invalid password');
const rawToken = await getUserTokenRaw({ id: user.id });
if (!rawToken) return badRequest(res, 'User does not have a token');
const token = encryptToken(rawToken, config.core.secret);
const cookie = serializeCookie('zipline_token', token, {
// week
maxAge: 60 * 60 * 24 * 7,
expires: new Date(Date.now() + 60 * 60 * 24 * 7 * 1000),
path: '/',
sameSite: 'lax',
});
res.setHeader('Set-Cookie', cookie);
delete user.password;
return ok(res, {
user,
token,
});
}
export default combine([cors(), method(['POST'])], handler);

View file

@ -0,0 +1,51 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/db';
import { log } from '@/lib/logger';
import { badRequest, ok, ratelimited, serverError } from '@/lib/response';
import { combine } from '@/lib/middleware/combine';
import { cors } from '@/lib/middleware/cors';
import { method } from '@/lib/middleware/method';
type Data = {
pass: boolean;
};
const ratelimit: Map<string, number> = new Map();
export async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
const logger = log('api').c('healthcheck');
const ip = (req.headers['x-forwarded-for'] as string) || req.socket.remoteAddress;
if (!ip) {
logger.debug(`request without an ip address blocked`);
return badRequest(res, 'no ip address found');
}
const last = ratelimit.get(ip);
if (last) {
if (last && Date.now() - last < 10000) {
logger.debug(`request from ${ip} blocked due to ratelimit`);
return ratelimited(res, Math.ceil((last + 10000 - Date.now()) / 1000));
} else {
ratelimit.delete(ip);
}
}
try {
await prisma.$queryRaw`SELECT 1;`;
ratelimit.set(ip, Date.now());
return ok(res, { pass: true });
} catch (e) {
logger.error('there was an error during a healthcheck').error(e);
ratelimit.set(ip, Date.now());
return serverError(res, 'there was an error during a healthcheck', {
pass: false,
});
}
}
export default combine([cors(), method(['GET'])], handler);

74
src/pages/api/setup.ts Normal file
View file

@ -0,0 +1,74 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/db';
import { log } from '@/lib/logger';
import { getZipline } from '@/lib/db/queries/zipline';
import { NextApiReq, NextApiRes, badRequest, forbidden, methodNotAllowed, ok } from '@/lib/response';
import { combine } from '@/lib/middleware/combine';
import { cors } from '@/lib/middleware/cors';
import { method } from '@/lib/middleware/method';
import { createToken, hashPassword } from '@/lib/crypto';
import { User } from '@/lib/db/queries/user';
type Response = {
firstSetup: boolean;
user: User;
};
type Body = {
username: string;
password: string;
};
export async function handler(req: NextApiReq<Body>, res: NextApiRes<Response>) {
const logger = log('api').c('setup');
const { firstSetup, id } = await getZipline();
if (!firstSetup) return forbidden(res);
logger.info('first setup running');
if (req.method === 'GET') {
return ok(res, { firstSetup });
}
const { username, password } = req.body;
if (!username) return badRequest(res, 'Username is required');
if (!password) return badRequest(res, 'Password is required');
if (password.length < 8) return badRequest(res, 'Password must be at least 8 characters long');
const user = await prisma.user.create({
data: {
username,
password: await hashPassword(password),
administrator: true,
token: createToken(),
},
select: {
administrator: true,
id: true,
createdAt: true,
updatedAt: true,
username: true,
},
});
logger.info('first setup complete');
await prisma.zipline.update({
where: {
id,
},
data: {
firstSetup: false,
},
});
return ok(res, {
firstSetup,
user,
});
}
export default combine([cors(), method(['GET', 'POST'])], handler);

View file

@ -0,0 +1,13 @@
import { User } from '@/lib/db/queries/user';
import { combine } from '@/lib/middleware/combine';
import { cors } from '@/lib/middleware/cors';
import { method } from '@/lib/middleware/method';
import { NextApiReq, NextApiRes } from '@/lib/response';
type Response = {
user: User;
};
export async function handler(req: NextApiReq, res: NextApiRes<Response>) {}
export default combine([cors(), method(['GET', 'POST', 'PATCH'])], handler);

13
src/pages/auth/login.tsx Normal file
View file

@ -0,0 +1,13 @@
import { Box, Center, Text, TextInput, Title } from '@mantine/core';
export default function Login() {
return (
<Box py='xl'>
<Center>
<Title order={1}>
<b>Zipline</b>
</Title>
</Center>
</Box>
);
}

13
src/pages/index.tsx Normal file
View file

@ -0,0 +1,13 @@
import Head from 'next/head';
export default function Home() {
return (
<>
<Head>
<title>Zipline</title>
<meta name='viewport' content='width=device-width, initial-scale=1' />
</Head>
<main>hi</main>
</>
);
}

View file

@ -1,58 +1,50 @@
import { validateEnv } from '@/lib/config/validate';
import { readEnv } from '@/lib/config/read';
import { createToken, decryptToken, encryptToken } from '@/lib/crypto';
import { runMigrations } from '@/lib/db/migration';
import { log } from '@/lib/logger';
import express from 'express';
import { join } from 'path';
import { createRequestHandler } from '@remix-run/express';
import { convertEnv } from 'src/lib/config/convert';
import { log } from 'src/lib/logger';
import { readEnv } from 'src/lib/config/read';
import { runMigrations } from 'src/lib/migration';
import next from 'next';
import { parse } from 'url';
const MODE = process.env.NODE_ENV || 'production';
const BUILD_DIR = join(process.cwd(), 'build');
const logger = log('server');
logger.info(`starting zipline in ${MODE} mode`);
async function main() {
logger.info(`starting zipline in ${MODE} mode`);
runMigrations().then(() => {});
const server = express();
const server = express();
const config = convertEnv(readEnv());
logger.info('reading environment for configuration');
const config = validateEnv(readEnv());
server.disable('x-powered-by');
process.env.DATABASE_URL = config.core.databaseUrl;
server.use('/modules', express.static('public/build', { maxAge: '1y', immutable: true }));
server.use(express.static('public', { maxAge: '1h' }));
await runMigrations();
server.all(
'*',
MODE === 'production'
? createRequestHandler({ build: require(BUILD_DIR) })
: (...args) => {
purgeRequireCache();
const requestHandler = createRequestHandler({
build: require(BUILD_DIR),
mode: MODE,
getLoadContext() {
return {
config,
};
},
});
return requestHandler(...args);
}
);
server.disable('x-powered-by');
server.use(express.static('public', { maxAge: '1h' }));
server.listen(3000, () => {
require(BUILD_DIR);
const app = next({
dev: MODE === 'development',
quiet: MODE === 'production',
hostname: config.core.hostname,
port: config.core.port,
dir: '.',
});
const handle = app.getRequestHandler();
logger.info(`server listening on port ${config.core.port}`);
});
await app.prepare();
function purgeRequireCache() {
for (const key in require.cache) {
if (key.startsWith(BUILD_DIR)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete require.cache[key];
}
}
server.all('*', (req, res) => {
const parsedUrl = parse(req.url!, true);
return handle(req, res, parsedUrl);
});
server.listen(config.core.port, config.core.hostname, () => {
logger.info(`server listening on port ${config.core.port}`);
});
}
main();

View file

@ -1,22 +1,23 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"target": "esnext",
"strict": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/app/*"],
"lib/*": ["src/lib/*"]
},
"forceConsistentCasingInFileNames": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"isolatedModules": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true
}
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

657
yarn.lock
View file

@ -3440,6 +3440,20 @@ __metadata:
languageName: node
linkType: hard
"@mantine/next@npm:^6.0.14":
version: 6.0.14
resolution: "@mantine/next@npm:6.0.14"
dependencies:
"@mantine/ssr": 6.0.14
"@mantine/styles": 6.0.14
peerDependencies:
next: "*"
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: d610f6f353b5184610fd2f1cfd51de47d9a7f2006f9c74661c7911ff64e86c41876404e6642a76fb549600579d9a164b4f0b1de645ceeec78094a6ab922a4525
languageName: node
linkType: hard
"@mantine/notifications@npm:^6.0.14":
version: 6.0.14
resolution: "@mantine/notifications@npm:6.0.14"
@ -3455,33 +3469,17 @@ __metadata:
languageName: node
linkType: hard
"@mantine/prism@npm:^6.0.14":
"@mantine/nprogress@npm:^6.0.14":
version: 6.0.14
resolution: "@mantine/prism@npm:6.0.14"
resolution: "@mantine/nprogress@npm:6.0.14"
dependencies:
"@mantine/utils": 6.0.14
prism-react-renderer: ^1.2.1
peerDependencies:
"@mantine/core": 6.0.14
"@mantine/hooks": 6.0.14
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: a5e554a4ff310949ee3104e50f7d7b9086046b2a2fdaa705ec6b18c152963f5d2a416e7a165a33f5b053c23a0cf7c95cc2bdc0b9ddd34b0abf9efacb91351c2c
languageName: node
linkType: hard
"@mantine/remix@npm:^6.0.14":
version: 6.0.14
resolution: "@mantine/remix@npm:6.0.14"
dependencies:
"@mantine/ssr": 6.0.14
"@mantine/styles": 6.0.14
peerDependencies:
"@mantine/core": 6.0.14
"@mantine/hooks": 6.0.14
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 52a131ebd7369ea240dd96464263e2f4440073ce32cddaa0810f5157924223828715a9644058a1d108e7a6bb56827ead474e110002f37dd771a6043bcb854219
checksum: a26d216f3869624a512409d148f5eea6edeee1f29688ef8113f7a5229e051ed3d5d0bed9a13746a53d2d490e391cd8ca5a5ea22133aa868532d920a498620722
languageName: node
linkType: hard
@ -3523,6 +3521,95 @@ __metadata:
languageName: node
linkType: hard
"@mapbox/node-pre-gyp@npm:^1.0.10":
version: 1.0.10
resolution: "@mapbox/node-pre-gyp@npm:1.0.10"
dependencies:
detect-libc: ^2.0.0
https-proxy-agent: ^5.0.0
make-dir: ^3.1.0
node-fetch: ^2.6.7
nopt: ^5.0.0
npmlog: ^5.0.1
rimraf: ^3.0.2
semver: ^7.3.5
tar: ^6.1.11
bin:
node-pre-gyp: bin/node-pre-gyp
checksum: 1a98db05d955b74dad3814679593df293b9194853698f3f5f1ed00ecd93128cdd4b14fb8767fe44ac6981ef05c23effcfdc88710e7c1de99ccb6f647890597c8
languageName: node
linkType: hard
"@next/env@npm:13.4.7":
version: 13.4.7
resolution: "@next/env@npm:13.4.7"
checksum: 5a2bba68fb8c80c87324025f10af7fe7319efdb15777247bfa8ff58e61bcc19b150bce4068396351e6c6df3344294cc06c03a2fb1bb0330659d230830a202c53
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-darwin-arm64@npm:13.4.7"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-darwin-x64@npm:13.4.7"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-linux-arm64-gnu@npm:13.4.7"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-linux-arm64-musl@npm:13.4.7"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-linux-x64-gnu@npm:13.4.7"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-linux-x64-musl@npm:13.4.7"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-win32-arm64-msvc@npm:13.4.7"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-win32-ia32-msvc@npm:13.4.7"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:13.4.7":
version: 13.4.7
resolution: "@next/swc-win32-x64-msvc@npm:13.4.7"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1":
version: 5.1.1-v1
resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1"
@ -3604,6 +3691,13 @@ __metadata:
languageName: node
linkType: hard
"@phc/format@npm:^1.0.0":
version: 1.0.0
resolution: "@phc/format@npm:1.0.0"
checksum: 15ee02504fbc16590923d89b1f1c2f5892df27cf2bf19180e5678511413e87b6e5355815a092749cd01698855ee5a0fc5d2393951c727acd650934eed290e26e
languageName: node
linkType: hard
"@pkgjs/parseargs@npm:^0.11.0":
version: 0.11.0
resolution: "@pkgjs/parseargs@npm:0.11.0"
@ -4041,47 +4135,6 @@ __metadata:
languageName: node
linkType: hard
"@remix-run/express@npm:^1.17.1":
version: 1.17.1
resolution: "@remix-run/express@npm:1.17.1"
dependencies:
"@remix-run/node": 1.17.1
peerDependencies:
express: ^4.17.1
checksum: b6109837f0f6d5c91794e137c68ea25eec40e43bcdecf63c6c80a3b38b1c27d34f06c91da54df6e3f2d16e6d65979f743fe58dd18bc85d2fe8bd7e38ef72a13f
languageName: node
linkType: hard
"@remix-run/node@npm:1.17.1, @remix-run/node@npm:^1.16.1":
version: 1.17.1
resolution: "@remix-run/node@npm:1.17.1"
dependencies:
"@remix-run/server-runtime": 1.17.1
"@remix-run/web-fetch": ^4.3.4
"@remix-run/web-file": ^3.0.2
"@remix-run/web-stream": ^1.0.3
"@web3-storage/multipart-parser": ^1.0.0
abort-controller: ^3.0.0
cookie-signature: ^1.1.0
source-map-support: ^0.5.21
stream-slice: ^0.1.2
checksum: 4e4d984ca1dc01f7c50983f99fbbbc97cb0629cac4cedad3970fc3591207c8d1ff690cfa07451dc3b1f9ad30ae75200bb21006b3793548d89a64daa40d5bf5eb
languageName: node
linkType: hard
"@remix-run/react@npm:^1.16.1":
version: 1.17.1
resolution: "@remix-run/react@npm:1.17.1"
dependencies:
"@remix-run/router": 1.6.3
react-router-dom: 6.13.0
peerDependencies:
react: ">=16.8"
react-dom: ">=16.8"
checksum: 59f573c7d78af33b77ae4df76895ccb70cd5e11f73653567732a3a32dd92d192c984a1ccd3d178df214e66505e80a30b8b26b03bc829dc98ed4452db977a3c43
languageName: node
linkType: hard
"@remix-run/router@npm:1.6.3":
version: 1.6.3
resolution: "@remix-run/router@npm:1.6.3"
@ -4102,69 +4155,6 @@ __metadata:
languageName: node
linkType: hard
"@remix-run/v1-route-convention@npm:^0.1.2":
version: 0.1.2
resolution: "@remix-run/v1-route-convention@npm:0.1.2"
dependencies:
minimatch: ^7.4.3
peerDependencies:
"@remix-run/dev": ^1.15.0
checksum: c057b4fd696e4bda869663be757fda2b647604ee65441882a2cf4aa5ea76e6630e293a4110817891cc8eab06574e04c60e6306f1e3b02dbfa21231882fc7a722
languageName: node
linkType: hard
"@remix-run/web-blob@npm:^3.0.3, @remix-run/web-blob@npm:^3.0.4":
version: 3.0.4
resolution: "@remix-run/web-blob@npm:3.0.4"
dependencies:
"@remix-run/web-stream": ^1.0.0
web-encoding: 1.1.5
checksum: 07d9a71d1795e8973cdc59c1a325aaaae7b9099a96815849355a34667d7a953cac78a332e02b25e0722d4d7244b7fe6d7ce6fc854e8baf83e42e8403f4a321fd
languageName: node
linkType: hard
"@remix-run/web-fetch@npm:^4.3.4":
version: 4.3.4
resolution: "@remix-run/web-fetch@npm:4.3.4"
dependencies:
"@remix-run/web-blob": ^3.0.4
"@remix-run/web-form-data": ^3.0.3
"@remix-run/web-stream": ^1.0.3
"@web3-storage/multipart-parser": ^1.0.0
abort-controller: ^3.0.0
data-uri-to-buffer: ^3.0.1
mrmime: ^1.0.0
checksum: 0c3370bfde1867722654734cddfab90982ba657a4244bf8e7e4aa49cb5e3c0b3429ed94b760d6881b8bf45d93bf411f29b9ffaf19332ecdc0bae1fa42b9a06fb
languageName: node
linkType: hard
"@remix-run/web-file@npm:^3.0.2":
version: 3.0.2
resolution: "@remix-run/web-file@npm:3.0.2"
dependencies:
"@remix-run/web-blob": ^3.0.3
checksum: f3bda87b62648e3ef0c0049aa560318d64adf493566a6446eae5a9d15a6080eb0c8ba1f450d4d7bbfa6cbad8c8d6a7adf4e72d546a4305ce0c05f63a95f80db0
languageName: node
linkType: hard
"@remix-run/web-form-data@npm:^3.0.3":
version: 3.0.4
resolution: "@remix-run/web-form-data@npm:3.0.4"
dependencies:
web-encoding: 1.1.5
checksum: 75c4c07c3307081d17d63b6d26209c651e5ccb910b96fa467415dcceb3f5e7c82d18cc34f604ffc5e85e2d3e77b93b9c2ef670b97745deccfe03cf85647cca17
languageName: node
linkType: hard
"@remix-run/web-stream@npm:^1.0.0, @remix-run/web-stream@npm:^1.0.3":
version: 1.0.3
resolution: "@remix-run/web-stream@npm:1.0.3"
dependencies:
web-streams-polyfill: ^3.1.1
checksum: 61a76b9e4ddb364fa5faa8cf28484f39800bd9259d4c2b96c235bd436e539d8bb00d433fcf79730d7bdf103255473fe0ccfde5d3c20a152f738c5bb102b26377
languageName: node
linkType: hard
"@rollup/pluginutils@npm:^4.0.0":
version: 4.2.1
resolution: "@rollup/pluginutils@npm:4.2.1"
@ -4244,6 +4234,15 @@ __metadata:
languageName: node
linkType: hard
"@swc/helpers@npm:0.5.1":
version: 0.5.1
resolution: "@swc/helpers@npm:0.5.1"
dependencies:
tslib: ^2.4.0
checksum: 71e0e27234590435e4c62b97ef5e796f88e786841a38c7116a5e27a3eafa7b9ead7cdec5249b32165902076de78446945311c973e59bddf77c1e24f33a8f272a
languageName: node
linkType: hard
"@szmarczak/http-timer@npm:^4.0.5":
version: 4.0.6
resolution: "@szmarczak/http-timer@npm:4.0.6"
@ -4253,6 +4252,25 @@ __metadata:
languageName: node
linkType: hard
"@tabler/icons-react@npm:^2.22.0":
version: 2.22.0
resolution: "@tabler/icons-react@npm:2.22.0"
dependencies:
"@tabler/icons": 2.22.0
prop-types: ^15.7.2
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0
checksum: fe2a4c3e5483269ee178195746cdc4b8c1e150dec78aae4bbf3884f33dd819929a7ff1e53eb6ba1426b914ba710e13a30d0e70caf9e588b8df45e500f70941dd
languageName: node
linkType: hard
"@tabler/icons@npm:2.22.0":
version: 2.22.0
resolution: "@tabler/icons@npm:2.22.0"
checksum: 3f0aaa801e8739d841ac5d335fbaee41399aaa9eeae03eb21c1dbe77877a29ba3664f16c225323a0635fad6548aca40212220edfb2919797454ea1029c5a5b78
languageName: node
linkType: hard
"@tediousjs/connection-string@npm:^0.4.1":
version: 0.4.2
resolution: "@tediousjs/connection-string@npm:0.4.2"
@ -4444,6 +4462,13 @@ __metadata:
languageName: node
linkType: hard
"@types/http-errors@npm:*":
version: 2.0.1
resolution: "@types/http-errors@npm:2.0.1"
checksum: 3bb0c50b0a652e679a84c30cd0340d696c32ef6558518268c238840346c077f899315daaf1c26c09c57ddd5dc80510f2a7f46acd52bf949e339e35ed3ee9654f
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.9":
version: 7.0.12
resolution: "@types/json-schema@npm:7.0.12"
@ -4621,12 +4646,13 @@ __metadata:
linkType: hard
"@types/serve-static@npm:*":
version: 1.15.1
resolution: "@types/serve-static@npm:1.15.1"
version: 1.15.2
resolution: "@types/serve-static@npm:1.15.2"
dependencies:
"@types/http-errors": "*"
"@types/mime": "*"
"@types/node": "*"
checksum: 2e078bdc1e458c7dfe69e9faa83cc69194b8896cce57cb745016580543c7ab5af07fdaa8ac1765eb79524208c81017546f66056f44d1204f812d72810613de36
checksum: 15c261dbfc57890f7cc17c04d5b22b418dfa0330c912b46c5d8ae2064da5d6f844ef7f41b63c7f4bbf07675e97ebe6ac804b032635ec742ae45d6f1274259b3e
languageName: node
linkType: hard
@ -4847,29 +4873,13 @@ __metadata:
languageName: node
linkType: hard
"@zxing/text-encoding@npm:0.9.0":
version: 0.9.0
resolution: "@zxing/text-encoding@npm:0.9.0"
checksum: c23b12aee7639382e4949961304a1294776afaffa40f579e09ffecd0e5e68cf26ef3edd75009de46da8a536e571448755ca68b3e2ea707d53793c0edb2e2c34a
languageName: node
linkType: hard
"abbrev@npm:^1.0.0":
"abbrev@npm:1, abbrev@npm:^1.0.0":
version: 1.1.1
resolution: "abbrev@npm:1.1.1"
checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17
languageName: node
linkType: hard
"abort-controller@npm:^3.0.0":
version: 3.0.0
resolution: "abort-controller@npm:3.0.0"
dependencies:
event-target-shim: ^5.0.0
checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75
languageName: node
linkType: hard
"accepts@npm:~1.3.8":
version: 1.3.8
resolution: "accepts@npm:1.3.8"
@ -5075,6 +5085,16 @@ __metadata:
languageName: node
linkType: hard
"are-we-there-yet@npm:^2.0.0":
version: 2.0.0
resolution: "are-we-there-yet@npm:2.0.0"
dependencies:
delegates: ^1.0.0
readable-stream: ^3.6.0
checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c
languageName: node
linkType: hard
"are-we-there-yet@npm:^3.0.0":
version: 3.0.1
resolution: "are-we-there-yet@npm:3.0.1"
@ -5092,6 +5112,17 @@ __metadata:
languageName: node
linkType: hard
"argon2@npm:^0.30.3":
version: 0.30.3
resolution: "argon2@npm:0.30.3"
dependencies:
"@mapbox/node-pre-gyp": ^1.0.10
"@phc/format": ^1.0.0
node-addon-api: ^5.0.0
checksum: 36784f69af8adad1e0e155a0f1999320999a3b76fb41a3b8f4674e1d896dc65a6e76bea4385b9433e8f935d0912a9ca81088b5d8d22e5fe4119d5abf9d10761f
languageName: node
linkType: hard
"argparse@npm:^2.0.1":
version: 2.0.1
resolution: "argparse@npm:2.0.1"
@ -5576,6 +5607,15 @@ __metadata:
languageName: node
linkType: hard
"busboy@npm:1.6.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
dependencies:
streamsearch: ^1.1.0
checksum: 32801e2c0164e12106bf236291a00795c3c4e4b709ae02132883fe8478ba2ae23743b11c5735a0aae8afe65ac4b6ca4568b91f0d9fed1fdbc32ede824a73746e
languageName: node
linkType: hard
"bytes@npm:3.1.2, bytes@npm:^3.1.2":
version: 3.1.2
resolution: "bytes@npm:3.1.2"
@ -5675,6 +5715,13 @@ __metadata:
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.30001406":
version: 1.0.30001507
resolution: "caniuse-lite@npm:1.0.30001507"
checksum: 7044172bdf65140c927cdaaff50368a97676f06a9fd8b515c046613bdf52cb769e9efb832ee491b8f8cc21f82c15f154a896efbab690f431bb064c95f3a2b7a8
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.30001503":
version: 1.0.30001506
resolution: "caniuse-lite@npm:1.0.30001506"
@ -5833,6 +5880,13 @@ __metadata:
languageName: node
linkType: hard
"client-only@npm:0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
checksum: 0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8
languageName: node
linkType: hard
"clone-response@npm:^1.0.2":
version: 1.0.3
resolution: "clone-response@npm:1.0.3"
@ -5888,7 +5942,7 @@ __metadata:
languageName: node
linkType: hard
"color-support@npm:^1.1.3":
"color-support@npm:^1.1.2, color-support@npm:^1.1.3":
version: 1.1.3
resolution: "color-support@npm:1.1.3"
bin:
@ -5960,7 +6014,7 @@ __metadata:
languageName: node
linkType: hard
"console-control-strings@npm:^1.1.0":
"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0":
version: 1.1.0
resolution: "console-control-strings@npm:1.1.0"
checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed
@ -5997,13 +6051,6 @@ __metadata:
languageName: node
linkType: hard
"cookie-signature@npm:^1.1.0":
version: 1.2.1
resolution: "cookie-signature@npm:1.2.1"
checksum: bb464aacac390b5d7d8ead2d6fff7c1c3b7378c7d0250921f48923fe889688e081ab33950448929db5f24d4f9f1506589a7ee1c685de8f12a3fdb30c49667ec5
languageName: node
linkType: hard
"cookie@npm:0.5.0":
version: 0.5.0
resolution: "cookie@npm:0.5.0"
@ -6146,7 +6193,7 @@ __metadata:
languageName: node
linkType: hard
"data-uri-to-buffer@npm:3, data-uri-to-buffer@npm:^3.0.1":
"data-uri-to-buffer@npm:3":
version: 3.0.1
resolution: "data-uri-to-buffer@npm:3.0.1"
checksum: c59c3009686a78c071806b72f4810856ec28222f0f4e252aa495ec027ed9732298ceea99c50328cf59b151dd34cbc3ad6150bbb43e41fc56fa19f48c99e9fc30
@ -6404,6 +6451,13 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.0":
version: 2.0.1
resolution: "detect-libc@npm:2.0.1"
checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7
languageName: node
linkType: hard
"detect-newline@npm:3.1.0":
version: 3.1.0
resolution: "detect-newline@npm:3.1.0"
@ -7506,13 +7560,6 @@ __metadata:
languageName: node
linkType: hard
"event-target-shim@npm:^5.0.0":
version: 5.0.1
resolution: "event-target-shim@npm:5.0.1"
checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166
languageName: node
linkType: hard
"events@npm:^3.0.0":
version: 3.3.0
resolution: "events@npm:3.3.0"
@ -8024,6 +8071,23 @@ __metadata:
languageName: node
linkType: hard
"gauge@npm:^3.0.0":
version: 3.0.2
resolution: "gauge@npm:3.0.2"
dependencies:
aproba: ^1.0.3 || ^2.0.0
color-support: ^1.1.2
console-control-strings: ^1.0.0
has-unicode: ^2.0.1
object-assign: ^4.1.1
signal-exit: ^3.0.0
string-width: ^4.2.3
strip-ansi: ^6.0.1
wide-align: ^1.1.2
checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9
languageName: node
linkType: hard
"gauge@npm:^4.0.3":
version: 4.0.4
resolution: "gauge@npm:4.0.4"
@ -8163,6 +8227,13 @@ __metadata:
languageName: node
linkType: hard
"glob-to-regexp@npm:^0.4.1":
version: 0.4.1
resolution: "glob-to-regexp@npm:0.4.1"
checksum: e795f4e8f06d2a15e86f76e4d92751cf8bbfcf0157cea5c2f0f35678a8195a750b34096b1256e436f0cebc1883b5ff0888c47348443e69546a5a87f9e1eb1167
languageName: node
linkType: hard
"glob@npm:7.1.6":
version: 7.1.6
resolution: "glob@npm:7.1.6"
@ -8842,7 +8913,7 @@ __metadata:
languageName: node
linkType: hard
"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1":
"is-arguments@npm:^1.1.1":
version: 1.1.1
resolution: "is-arguments@npm:1.1.1"
dependencies:
@ -8976,15 +9047,6 @@ __metadata:
languageName: node
linkType: hard
"is-generator-function@npm:^1.0.7":
version: 1.0.10
resolution: "is-generator-function@npm:1.0.10"
dependencies:
has-tostringtag: ^1.0.0
checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b
languageName: node
linkType: hard
"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
version: 4.0.3
resolution: "is-glob@npm:4.0.3"
@ -9165,7 +9227,7 @@ __metadata:
languageName: node
linkType: hard
"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9":
"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9":
version: 1.1.10
resolution: "is-typed-array@npm:1.1.10"
dependencies:
@ -9248,13 +9310,6 @@ __metadata:
languageName: node
linkType: hard
"isbot@npm:^3.6.10":
version: 3.6.12
resolution: "isbot@npm:3.6.12"
checksum: e23782a6633bf60fc3db23171468fc3f4cdc36f5a707b0cf1f3b2aff2686bedb29d036411afec90a9de23901a251ff53596ab9ca31cc85223dd8f03392fc03e6
languageName: node
linkType: hard
"isexe@npm:^2.0.0":
version: 2.0.0
resolution: "isexe@npm:2.0.0"
@ -9832,7 +9887,7 @@ __metadata:
languageName: node
linkType: hard
"make-dir@npm:3.1.0, make-dir@npm:^3.0.0, make-dir@npm:^3.0.2":
"make-dir@npm:3.1.0, make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0":
version: 3.1.0
resolution: "make-dir@npm:3.1.0"
dependencies:
@ -10540,15 +10595,6 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^7.4.3":
version: 7.4.6
resolution: "minimatch@npm:7.4.6"
dependencies:
brace-expansion: ^2.0.1
checksum: 1a6c8d22618df9d2a88aabeef1de5622eb7b558e9f8010be791cb6b0fa6e102d39b11c28d75b855a1e377b12edc7db8ff12a99c20353441caa6a05e78deb5da9
languageName: node
linkType: hard
"minimatch@npm:^9.0.0, minimatch@npm:^9.0.1":
version: 9.0.1
resolution: "minimatch@npm:9.0.1"
@ -10756,13 +10802,6 @@ __metadata:
languageName: node
linkType: hard
"mrmime@npm:^1.0.0":
version: 1.0.1
resolution: "mrmime@npm:1.0.1"
checksum: cc979da44bbbffebaa8eaf7a45117e851f2d4cb46a3ada6ceb78130466a04c15a0de9a9ce1c8b8ba6f6e1b8618866b1352992bf1757d241c0ddca558b9f28a77
languageName: node
linkType: hard
"ms@npm:2.0.0":
version: 2.0.0
resolution: "ms@npm:2.0.0"
@ -10828,7 +10867,7 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^3.3.6":
"nanoid@npm:^3.3.4, nanoid@npm:^3.3.6":
version: 3.3.6
resolution: "nanoid@npm:3.3.6"
bin:
@ -10879,6 +10918,65 @@ __metadata:
languageName: node
linkType: hard
"next@npm:^13.4.7":
version: 13.4.7
resolution: "next@npm:13.4.7"
dependencies:
"@next/env": 13.4.7
"@next/swc-darwin-arm64": 13.4.7
"@next/swc-darwin-x64": 13.4.7
"@next/swc-linux-arm64-gnu": 13.4.7
"@next/swc-linux-arm64-musl": 13.4.7
"@next/swc-linux-x64-gnu": 13.4.7
"@next/swc-linux-x64-musl": 13.4.7
"@next/swc-win32-arm64-msvc": 13.4.7
"@next/swc-win32-ia32-msvc": 13.4.7
"@next/swc-win32-x64-msvc": 13.4.7
"@swc/helpers": 0.5.1
busboy: 1.6.0
caniuse-lite: ^1.0.30001406
postcss: 8.4.14
styled-jsx: 5.1.1
watchpack: 2.4.0
zod: 3.21.4
peerDependencies:
"@opentelemetry/api": ^1.1.0
fibers: ">= 3.1.0"
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
dependenciesMeta:
"@next/swc-darwin-arm64":
optional: true
"@next/swc-darwin-x64":
optional: true
"@next/swc-linux-arm64-gnu":
optional: true
"@next/swc-linux-arm64-musl":
optional: true
"@next/swc-linux-x64-gnu":
optional: true
"@next/swc-linux-x64-musl":
optional: true
"@next/swc-win32-arm64-msvc":
optional: true
"@next/swc-win32-ia32-msvc":
optional: true
"@next/swc-win32-x64-msvc":
optional: true
peerDependenciesMeta:
"@opentelemetry/api":
optional: true
fibers:
optional: true
sass:
optional: true
bin:
next: dist/bin/next
checksum: 76026a5def68c00064bc4860cd15a5f292220ccc73ff24245b3658a90a46f66c290d3543a59e1cb91310145141d4ad1238d7cf652f41f47cdf434ab8705af7d1
languageName: node
linkType: hard
"nice-try@npm:^1.0.4":
version: 1.0.5
resolution: "nice-try@npm:1.0.5"
@ -10902,7 +11000,16 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:2.6.11, node-fetch@npm:^2.6.9":
"node-addon-api@npm:^5.0.0":
version: 5.1.0
resolution: "node-addon-api@npm:5.1.0"
dependencies:
node-gyp: latest
checksum: 2508bd2d2981945406243a7bd31362fc7af8b70b8b4d65f869c61731800058fb818cc2fd36c8eac714ddd0e568cc85becf5e165cebbdf7b5024d5151bbc75ea1
languageName: node
linkType: hard
"node-fetch@npm:2.6.11, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9":
version: 2.6.11
resolution: "node-fetch@npm:2.6.11"
dependencies:
@ -10944,6 +11051,17 @@ __metadata:
languageName: node
linkType: hard
"nopt@npm:^5.0.0":
version: 5.0.0
resolution: "nopt@npm:5.0.0"
dependencies:
abbrev: 1
bin:
nopt: bin/nopt.js
checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f
languageName: node
linkType: hard
"nopt@npm:^6.0.0":
version: 6.0.0
resolution: "nopt@npm:6.0.0"
@ -11050,6 +11168,18 @@ __metadata:
languageName: node
linkType: hard
"npmlog@npm:^5.0.1":
version: 5.0.1
resolution: "npmlog@npm:5.0.1"
dependencies:
are-we-there-yet: ^2.0.0
console-control-strings: ^1.1.0
gauge: ^3.0.0
set-blocking: ^2.0.0
checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f
languageName: node
linkType: hard
"npmlog@npm:^6.0.0":
version: 6.0.2
resolution: "npmlog@npm:6.0.2"
@ -11835,6 +11965,17 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:8.4.14":
version: 8.4.14
resolution: "postcss@npm:8.4.14"
dependencies:
nanoid: ^3.3.4
picocolors: ^1.0.0
source-map-js: ^1.0.2
checksum: fe58766ff32e4becf65a7d57678995cfd239df6deed2fe0557f038b47c94e4132e7e5f68b5aa820c13adfec32e523b693efaeb65798efb995ce49ccd83953816
languageName: node
linkType: hard
"postcss@npm:^8.4.19, postcss@npm:^8.4.23":
version: 8.4.24
resolution: "postcss@npm:8.4.24"
@ -11919,15 +12060,6 @@ __metadata:
languageName: node
linkType: hard
"prism-react-renderer@npm:^1.2.1":
version: 1.3.5
resolution: "prism-react-renderer@npm:1.3.5"
peerDependencies:
react: ">=0.14.9"
checksum: c18806dcbc4c0b4fd6fd15bd06b4f7c0a6da98d93af235c3e970854994eb9b59e23315abb6cfc29e69da26d36709a47e25da85ab27fed81b6812f0a52caf6dfa
languageName: node
linkType: hard
"prisma@npm:^4.16.1":
version: 4.16.1
resolution: "prisma@npm:4.16.1"
@ -11981,7 +12113,7 @@ __metadata:
languageName: node
linkType: hard
"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@ -12212,30 +12344,6 @@ __metadata:
languageName: node
linkType: hard
"react-router-dom@npm:6.13.0":
version: 6.13.0
resolution: "react-router-dom@npm:6.13.0"
dependencies:
"@remix-run/router": 1.6.3
react-router: 6.13.0
peerDependencies:
react: ">=16.8"
react-dom: ">=16.8"
checksum: f51131063c2d5e127b6b3f3f813c6d4988d0f37694a06697dc9d4a4d9d3825e2a4487ec9b81a1d356eb269018814d884ffc2e3d9ff056a46ae59c99c9e7e1086
languageName: node
linkType: hard
"react-router@npm:6.13.0":
version: 6.13.0
resolution: "react-router@npm:6.13.0"
dependencies:
"@remix-run/router": 1.6.3
peerDependencies:
react: ">=16.8"
checksum: 31a187005d05e063c59324564a283cd28052eaf848ad446c87658f4fc48fa9543329fe8a14d7be14d9bbf62410d383f8cf1cf13898a838bf9c1e3201fe93384c
languageName: node
linkType: hard
"react-style-singleton@npm:^2.2.1":
version: 2.2.1
resolution: "react-style-singleton@npm:2.2.1"
@ -12958,7 +13066,7 @@ __metadata:
languageName: node
linkType: hard
"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7":
"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7":
version: 3.0.7
resolution: "signal-exit@npm:3.0.7"
checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318
@ -13236,10 +13344,10 @@ __metadata:
languageName: node
linkType: hard
"stream-slice@npm:^0.1.2":
version: 0.1.2
resolution: "stream-slice@npm:0.1.2"
checksum: 027111397bd709f299fb1bb34902baf4707bba8851219c9115df673be1075a2cecf54d8671e6258c94483d1fa4e931c6784e49f2e005b1b6d5e3b8b61028fbe1
"streamsearch@npm:^1.1.0":
version: 1.1.0
resolution: "streamsearch@npm:1.1.0"
checksum: 1cce16cea8405d7a233d32ca5e00a00169cc0e19fbc02aa839959985f267335d435c07f96e5e0edd0eadc6d39c98d5435fb5bbbdefc62c41834eadc5622ad942
languageName: node
linkType: hard
@ -13456,6 +13564,22 @@ __metadata:
languageName: node
linkType: hard
"styled-jsx@npm:5.1.1":
version: 5.1.1
resolution: "styled-jsx@npm:5.1.1"
dependencies:
client-only: 0.0.1
peerDependencies:
react: ">= 16.8.0 || 17.x.x || ^18.0.0-0"
peerDependenciesMeta:
"@babel/core":
optional: true
babel-plugin-macros:
optional: true
checksum: 523a33b38603492547e861b98e29c873939b04e15fbe5ef16132c6f1e15958126647983c7d4675325038b428a5e91183d996e90141b18bdd1bbadf6e2c45b2fa
languageName: node
linkType: hard
"stylis@npm:4.2.0":
version: 4.2.0
resolution: "stylis@npm:4.2.0"
@ -14313,19 +14437,6 @@ __metadata:
languageName: node
linkType: hard
"util@npm:^0.12.3":
version: 0.12.5
resolution: "util@npm:0.12.5"
dependencies:
inherits: ^2.0.3
is-arguments: ^1.0.4
is-generator-function: ^1.0.7
is-typed-array: ^1.1.3
which-typed-array: ^1.1.2
checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a
languageName: node
linkType: hard
"utils-merge@npm:1.0.1":
version: 1.0.1
resolution: "utils-merge@npm:1.0.1"
@ -14480,6 +14591,16 @@ __metadata:
languageName: node
linkType: hard
"watchpack@npm:2.4.0":
version: 2.4.0
resolution: "watchpack@npm:2.4.0"
dependencies:
glob-to-regexp: ^0.4.1
graceful-fs: ^4.1.2
checksum: 23d4bc58634dbe13b86093e01c6a68d8096028b664ab7139d58f0c37d962d549a940e98f2f201cecdabd6f9c340338dc73ef8bf094a2249ef582f35183d1a131
languageName: node
linkType: hard
"wcwidth@npm:^1.0.1":
version: 1.0.1
resolution: "wcwidth@npm:1.0.1"
@ -14489,26 +14610,6 @@ __metadata:
languageName: node
linkType: hard
"web-encoding@npm:1.1.5":
version: 1.1.5
resolution: "web-encoding@npm:1.1.5"
dependencies:
"@zxing/text-encoding": 0.9.0
util: ^0.12.3
dependenciesMeta:
"@zxing/text-encoding":
optional: true
checksum: 2234a2b122f41006ce07859b3c0bf2e18f46144fda2907d5db0b571b76aa5c26977c646100ad9c00d2f8a4f6f2b848bc02147845d8c447ab365ec4eff376338d
languageName: node
linkType: hard
"web-streams-polyfill@npm:^3.1.1":
version: 3.2.1
resolution: "web-streams-polyfill@npm:3.2.1"
checksum: b119c78574b6d65935e35098c2afdcd752b84268e18746606af149e3c424e15621b6f1ff0b42b2676dc012fc4f0d313f964b41a4b5031e525faa03997457da02
languageName: node
linkType: hard
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
@ -14586,7 +14687,7 @@ __metadata:
languageName: node
linkType: hard
"which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.9":
"which-typed-array@npm:^1.1.9":
version: 1.1.9
resolution: "which-typed-array@npm:1.1.9"
dependencies:
@ -14622,7 +14723,7 @@ __metadata:
languageName: node
linkType: hard
"wide-align@npm:^1.1.5":
"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5":
version: 1.1.5
resolution: "wide-align@npm:1.1.5"
dependencies:
@ -14807,24 +14908,22 @@ __metadata:
"@mantine/form": ^6.0.14
"@mantine/hooks": ^6.0.14
"@mantine/modals": ^6.0.14
"@mantine/next": ^6.0.14
"@mantine/notifications": ^6.0.14
"@mantine/prism": ^6.0.14
"@mantine/remix": ^6.0.14
"@mantine/nprogress": ^6.0.14
"@prisma/client": 4.16.1
"@prisma/internals": ^4.16.1
"@prisma/migrate": ^4.16.1
"@remix-run/dev": ^1.16.1
"@remix-run/eslint-config": ^1.16.1
"@remix-run/express": ^1.17.1
"@remix-run/node": ^1.16.1
"@remix-run/react": ^1.16.1
"@remix-run/v1-route-convention": ^0.1.2
"@tabler/icons-react": ^2.22.0
"@types/bytes": ^3.1.1
"@types/express": ^4.17.17
"@types/node": ^20.3.1
"@types/react": ^18.2.7
"@types/react-dom": ^18.2.4
"@types/signale": ^1.4.4
argon2: ^0.30.3
bytes: ^3.1.2
colorette: ^2.0.20
cross-env: ^7.0.3
@ -14832,8 +14931,8 @@ __metadata:
dotenv: ^16.1.3
eslint: ^8.41.0
express: ^4.18.2
isbot: ^3.6.10
ms: ^2.1.3
next: ^13.4.7
npm-run-all: ^4.1.5
prisma: ^4.16.1
react: ^18.2.0
@ -14856,7 +14955,7 @@ __metadata:
languageName: node
linkType: hard
"zod@npm:^3.21.4":
"zod@npm:3.21.4, zod@npm:^3.21.4":
version: 3.21.4
resolution: "zod@npm:3.21.4"
checksum: f185ba87342ff16f7a06686767c2b2a7af41110c7edf7c1974095d8db7a73792696bcb4a00853de0d2edeb34a5b2ea6a55871bc864227dace682a0a28de33e1f