diff --git a/.gitignore b/.gitignore index f09f4c3d..000cd898 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ yarn-error.log* # zipline config.toml -uploads/ \ No newline at end of file +uploads/ +dist/ +docker-compose.local.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 33cca007..ff885fd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,9 @@ WORKDIR /build COPY --from=deps /build/node_modules ./node_modules COPY src ./src -COPY server ./server COPY scripts ./scripts COPY prisma ./prisma -COPY package.json yarn.lock next.config.js next-env.d.ts zip-env.d.ts tsconfig.json ./ +COPY package.json yarn.lock esbuild.config.js next.config.js next-env.d.ts zip-env.d.ts tsconfig.json ./ ENV ZIPLINE_DOCKER_BUILD 1 ENV NEXT_TELEMETRY_DISABLED 1 @@ -31,11 +30,11 @@ RUN addgroup --system --gid 1001 zipline RUN adduser --system --uid 1001 zipline COPY --from=builder --chown=zipline:zipline /build/.next ./.next +COPY --from=builder --chown=zipline:zipline /build/dist ./dist COPY --from=builder --chown=zipline:zipline /build/node_modules ./node_modules COPY --from=builder /build/next.config.js ./next.config.js COPY --from=builder /build/src ./src -COPY --from=builder /build/server ./server COPY --from=builder /build/scripts ./scripts COPY --from=builder /build/prisma ./prisma COPY --from=builder /build/tsconfig.json ./tsconfig.json @@ -43,4 +42,4 @@ COPY --from=builder /build/package.json ./package.json USER zipline -CMD ["node", "server"] \ No newline at end of file +CMD ["node", "dist/server"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index c228d422..fd27359e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 dicedtomato +Copyright (c) 2022 dicedtomato Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c43b6962..071ebfd1 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -26,11 +26,12 @@ services: - SECRET=changethis - HOST=0.0.0.0 - PORT=3000 + - DATASOURCE_TYPE=local + - DATASOURCE_DIRECTORY=./uploads - DATABASE_URL=postgresql://postgres:postgres@postgres/postgres/ - UPLOADER_ROUTE=/u - UPLOADER_EMBED_ROUTE=/a - UPLOADER_LENGTH=6 - - UPLOADER_DIRECTORY=./uploads - UPLOADER_ADMIN_LIMIT=104900000 - UPLOADER_USER_LIMIT=104900000 - UPLOADER_DISABLED_EXTS= diff --git a/docker-compose.yml b/docker-compose.yml index 2272c011..2fe3fefa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,11 +24,12 @@ services: - SECRET=changethis - HOST=0.0.0.0 - PORT=3000 + - DATASOURCE_TYPE=local + - DATASOURCE_DIRECTORY=./uploads - DATABASE_URL=postgresql://postgres:postgres@postgres/postgres/ - UPLOADER_ROUTE=/u - UPLOADER_EMBED_ROUTE=/a - UPLOADER_LENGTH=6 - - UPLOADER_DIRECTORY=./uploads - UPLOADER_ADMIN_LIMIT=104900000 - UPLOADER_USER_LIMIT=104900000 - UPLOADER_DISABLED_EXTS= diff --git a/esbuild.config.js b/esbuild.config.js new file mode 100644 index 00000000..209831ff --- /dev/null +++ b/esbuild.config.js @@ -0,0 +1,33 @@ +const esbuild = require('esbuild'); + +(async () => { + const watch = process.argv[2] === '--watch'; + + await esbuild.build({ + tsconfig: 'tsconfig.json', + outdir: 'dist', + bundle: false, + platform: 'node', + treeShaking: true, + entryPoints: [ + 'src/server/index.ts', + 'src/server/util.ts', + 'src/server/validateConfig.ts', + 'src/lib/logger.ts', + 'src/lib/readConfig.ts', + 'src/lib/datasource/datasource.ts', + 'src/lib/datasource/index.ts', + 'src/lib/datasource/Local.ts', + 'src/lib/datasource/S3.ts', + 'src/lib/ds.ts', + 'src/lib/config.ts', + ], + format: 'cjs', + resolveExtensions: ['.ts', '.js'], + write: true, + watch, + incremental: watch, + sourcemap: false, + minify: process.env.NODE_ENV === 'production', + }); +})(); \ No newline at end of file diff --git a/package.json b/package.json index 3c547abb..a0af9271 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "zip3", - "version": "3.4.0", + "version": "3.4.1", "license": "MIT", "scripts": { - "dev": "NODE_ENV=development node server", - "build": "npm-run-all build:schema build:next", + "dev": "node esbuild.config.js && REACT_EDITOR=code-insiders NODE_ENV=development node dist/server", + "build": "npm-run-all build:server build:schema build:next", + "build:server": "node esbuild.config.js", "build:next": "next build", "build:schema": "prisma generate --schema=prisma/schema.prisma", "migrate:dev": "prisma migrate dev --create-only", @@ -30,6 +31,7 @@ "@prisma/sdk": "^3.9.2", "@reduxjs/toolkit": "^1.6.0", "argon2": "^0.28.2", + "aws-sdk": "^2.1085.0", "colorette": "^1.2.2", "cookie": "^0.4.1", "fecha": "^4.2.1", @@ -50,6 +52,7 @@ "@types/multer": "^1.4.6", "@types/node": "^15.12.2", "babel-plugin-import": "^1.13.3", + "esbuild": "^0.14.23", "eslint": "^7.32.0", "eslint-config-next": "11.0.0", "npm-run-all": "^4.1.5", diff --git a/server/validateConfig.js b/server/validateConfig.js deleted file mode 100644 index 1ae84710..00000000 --- a/server/validateConfig.js +++ /dev/null @@ -1,40 +0,0 @@ -const { object, bool, string, number, boolean, array } = require('yup'); - -const validator = object({ - core: object({ - secure: bool().default(false), - secret: string().min(8).required(), - host: string().default('0.0.0.0'), - port: number().default(3000), - database_url: string().required(), - logger: boolean().default(false), - stats_interval: number().default(1800), - }).required(), - uploader: object({ - route: string().default('/u'), - embed_route: string().default('/a'), - length: number().default(6), - directory: string().default('./uploads'), - admin_limit: number().default(104900000), - user_limit: number().default(104900000), - disabled_extensions: array().default([]), - }).required(), - urls: object({ - route: string().default('/go'), - length: number().default(6), - }).required(), - ratelimit: object({ - user: number().default(0), - admin: number().default(0), - }), -}); - - -module.exports = function validate(config) { - try { - return validator.validateSync(config, { abortEarly: false }); - } catch (e) { - if (process.env.ZIPLINE_DOCKER_BUILD) return {}; - throw `${e.errors.length} errors occured\n${e.errors.map(x => '\t' + x).join('\n')}`; - } -}; \ No newline at end of file diff --git a/src/components/ImagesTable.tsx b/src/components/ImagesTable.tsx index 237ef789..21dbe569 100644 --- a/src/components/ImagesTable.tsx +++ b/src/components/ImagesTable.tsx @@ -8,7 +8,6 @@ import { } from 'react-table'; import { ActionIcon, - Checkbox, createStyles, Divider, Group, diff --git a/src/components/StatText.tsx b/src/components/StatText.tsx new file mode 100644 index 00000000..23ee0bfd --- /dev/null +++ b/src/components/StatText.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Text } from '@mantine/core'; + +export default function StatText({ children }) { + return {children}; +} \ No newline at end of file diff --git a/src/components/pages/Dashboard.tsx b/src/components/pages/Dashboard.tsx index 1b15713e..ab740829 100644 --- a/src/components/pages/Dashboard.tsx +++ b/src/components/pages/Dashboard.tsx @@ -1,15 +1,16 @@ import React, { useEffect, useState } from 'react'; import Card from 'components/Card'; -import Image from 'components/Image'; +import ZiplineImage from 'components/Image'; import ImagesTable from 'components/ImagesTable'; import useFetch from 'lib/hooks/useFetch'; import { useStoreSelector } from 'lib/redux/store'; -import { Box, Text, Table, Skeleton, Title, SimpleGrid } from '@mantine/core'; +import { Text, Skeleton, Title, SimpleGrid } from '@mantine/core'; import { randomId, useClipboard } from '@mantine/hooks'; import Link from 'components/Link'; import { CopyIcon, Cross1Icon, TrashIcon } from '@modulz/radix-icons'; import { useNotifications } from '@mantine/notifications'; +import StatText from 'components/StatText'; type Aligns = 'inherit' | 'right' | 'left' | 'center' | 'justify'; @@ -27,37 +28,6 @@ export function bytesToRead(bytes: number) { return `${bytes.toFixed(1)} ${units[num]}`; } -function StatText({ children }) { - return {children}; -} - -function StatTable({ rows, columns }) { - return ( - - - - - {columns.map(col => ( - - ))} - - - - {rows.map(row => ( - - {columns.map(col => ( - - ))} - - ))} - -
{col.name}
- {col.format ? col.format(row[col.id]) : row[col.id]} -
-
- ); -} - export default function Dashboard() { const user = useStoreSelector(state => state.user); @@ -128,8 +98,7 @@ export default function Dashboard() { ]} > {recent.length ? recent.map(image => ( - // eslint-disable-next-line jsx-a11y/alt-text - + )) : [1,2,3,4].map(x => (
diff --git a/src/components/pages/Stats.tsx b/src/components/pages/Stats.tsx index 3727e95e..809acefb 100644 --- a/src/components/pages/Stats.tsx +++ b/src/components/pages/Stats.tsx @@ -1,17 +1,11 @@ import React, { useEffect, useState } from 'react'; import Card from 'components/Card'; -import Image from 'components/Image'; -import ImagesTable from 'components/ImagesTable'; +import StatText from 'components/StatText'; import useFetch from 'lib/hooks/useFetch'; import { useStoreSelector } from 'lib/redux/store'; import { Box, Text, Table, Skeleton, Title, SimpleGrid } from '@mantine/core'; -import { randomId, useClipboard } from '@mantine/hooks'; -import Link from 'components/Link'; -import { CopyIcon, Cross1Icon, TrashIcon } from '@modulz/radix-icons'; -import { useNotifications } from '@mantine/notifications'; - -type Aligns = 'inherit' | 'right' | 'left' | 'center' | 'justify'; +import { randomId } from '@mantine/hooks'; export function bytesToRead(bytes: number) { if (isNaN(bytes)) return '0.0 B'; @@ -27,10 +21,6 @@ export function bytesToRead(bytes: number) { return `${bytes.toFixed(1)} ${units[num]}`; } -function StatText({ children }) { - return {children}; -} - function StatTable({ rows, columns }) { return ( @@ -58,9 +48,7 @@ function StatTable({ rows, columns }) { ); } -export default function Dashboard() { - const user = useStoreSelector(state => state.user); - +export default function Stats() { const [stats, setStats] = useState(null); const update = async () => { diff --git a/src/components/pages/Upload.tsx b/src/components/pages/Upload.tsx index 9d0adbf1..9c33fe5f 100644 --- a/src/components/pages/Upload.tsx +++ b/src/components/pages/Upload.tsx @@ -30,7 +30,7 @@ function getIconColor(status, theme) { : theme.black; } -export default function Upload({ route }) { +export default function Upload() { const theme = useMantineTheme(); const notif = useNotifications(); const clipboard = useClipboard(); @@ -89,10 +89,8 @@ export default function Upload({ route }) { return ( <> - setFiles([...files, ...f])} - > - {(status) => ( + setFiles([...files, ...f])}> + {status => ( <> { + await writeFile(join(process.cwd(), this.path, file), data); + } + + public async get(file: string): Promise { + try { + const data = await readFile(join(process.cwd(), this.path, file)); + return data; + } catch (e) { + return null; + } + } + + public async size(): Promise { + const files = await readdir(this.path); + + let size = 0; + for (let i = 0, L = files.length; i !== L; ++i) { + const sta = await stat(join(this.path, files[i])); + size += sta.size; + } + + return size; + } +} \ No newline at end of file diff --git a/src/lib/datasource/S3.ts b/src/lib/datasource/S3.ts new file mode 100644 index 00000000..71499783 --- /dev/null +++ b/src/lib/datasource/S3.ts @@ -0,0 +1,66 @@ +import { Datasource } from './datasource'; +import AWS from 'aws-sdk'; + +export class S3 extends Datasource { + public name: string = 'S3'; + public s3: AWS.S3; + + public constructor( + public accessKey: string, + public secretKey: string, + public bucket: string, + ) { + super(); + this.s3 = new AWS.S3({ + accessKeyId: accessKey, + secretAccessKey: secretKey, + }); + } + + public async save(file: string, data: Buffer): Promise { + return new Promise((resolve, reject) => { + this.s3.upload({ + Bucket: this.bucket, + Key: file, + Body: data, + }, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + public async get(file: string): Promise { + return new Promise((resolve, reject) => { + this.s3.getObject({ + Bucket: this.bucket, + Key: file, + }, (err, data) => { + if (err) { + reject(err); + } else { + // @ts-ignore + resolve(Buffer.from(data.Body)); + } + }); + }); + } + + public async size(): Promise { + return new Promise((resolve, reject) => { + this.s3.listObjects({ + Bucket: this.bucket, + }, (err, data) => { + if (err) { + reject(err); + } else { + const size = data.Contents.reduce((acc, cur) => acc + cur.Size, 0); + resolve(size); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/lib/datasource/datasource.ts b/src/lib/datasource/datasource.ts new file mode 100644 index 00000000..f598fe5d --- /dev/null +++ b/src/lib/datasource/datasource.ts @@ -0,0 +1,7 @@ +export abstract class Datasource { + public name: string; + + public abstract save(file: string, data: Buffer): Promise; + public abstract get(file: string): Promise; + public abstract size(): Promise; +} \ No newline at end of file diff --git a/src/lib/datasource/index.ts b/src/lib/datasource/index.ts new file mode 100644 index 00000000..6290a447 --- /dev/null +++ b/src/lib/datasource/index.ts @@ -0,0 +1,4 @@ +export { Datasource } from './datasource'; +export { Local } from './Local'; +export { S3 } from './S3'; + diff --git a/src/lib/ds.ts b/src/lib/ds.ts new file mode 100644 index 00000000..9988f508 --- /dev/null +++ b/src/lib/ds.ts @@ -0,0 +1,20 @@ +import config from './config'; +import { S3, Local } from './datasource'; +import Logger from './logger'; + +if (!global.datasource) { + switch (config.datasource.type) { + case 's3': + global.datasource = new S3(config.datasource.s3.access_key_id, config.datasource.s3.secret_access_key, config.datasource.s3.bucket); + Logger.get('datasource').info(`Using S3:${config.datasource.s3.bucket} datasource`); + break; + case 'local': + global.datasource = new Local(config.datasource.local.directory); + Logger.get('datasource').info(`Using local:${config.datasource.local.directory} datasource`); + break; + default: + throw new Error('Invalid datasource type'); + } +}; + +export default global.datasource; \ No newline at end of file diff --git a/src/lib/logger.js b/src/lib/logger.js deleted file mode 100644 index 9490ac72..00000000 --- a/src/lib/logger.js +++ /dev/null @@ -1,38 +0,0 @@ -const { format } = require('fecha'); -const { blueBright, red, cyan } = require('colorette'); - -module.exports = class Logger { - static get(clas) { - if (typeof clas !== 'function') if (typeof clas !== 'string') throw new Error('not string/function'); - - const name = clas.name ?? clas; - - return new Logger(name); - } - - constructor (name) { - this.name = name; - } - - info(message) { - console.log(this.formatMessage('INFO', this.name, message)); - } - - error(error) { - console.log(this.formatMessage('ERROR', this.name, error.stack ?? error)); - } - - formatMessage(level, name, message) { - const time = format(new Date(), 'YYYY-MM-DD hh:mm:ss,SSS A'); - return `${time} ${this.formatLevel(level)} [${blueBright(name)}] ${message}`; - } - - formatLevel(level) { - switch (level) { - case 'INFO': - return cyan('INFO '); - case 'ERROR': - return red('ERROR'); - } - } -}; \ No newline at end of file diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 00000000..a8484150 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,45 @@ +import { format } from 'fecha'; +import { blueBright, red, cyan } from 'colorette'; + +export enum LoggerLevel { + ERROR, + INFO, +} + +export default class Logger { + public name: string; + + static get(clas: any) { + if (typeof clas !== 'function') if (typeof clas !== 'string') throw new Error('not string/function'); + + const name = clas.name ?? clas; + + return new Logger(name); + } + + constructor(name: string) { + this.name = name; + } + + info(message: string) { + console.log(this.formatMessage(LoggerLevel.INFO, this.name, message)); + } + + error(error: any) { + console.log(this.formatMessage(LoggerLevel.ERROR, this.name, error.stack ?? error)); + } + + formatMessage(level: LoggerLevel, name, message) { + const time = format(new Date(), 'YYYY-MM-DD hh:mm:ss,SSS A'); + return `${time} ${this.formatLevel(level)} [${blueBright(name)}] ${message}`; + } + + formatLevel(level: LoggerLevel) { + switch (level) { + case LoggerLevel.INFO: + return cyan('INFO '); + case LoggerLevel.ERROR: + return red('ERROR'); + } + } +}; \ No newline at end of file diff --git a/src/lib/middleware/withZipline.ts b/src/lib/middleware/withZipline.ts index 112ab26f..4fa26aca 100644 --- a/src/lib/middleware/withZipline.ts +++ b/src/lib/middleware/withZipline.ts @@ -11,7 +11,7 @@ export interface NextApiFile { originalname: string; encoding: string; mimetype: string; - buffer: string; + buffer: Buffer; size: number; } diff --git a/src/lib/readConfig.js b/src/lib/readConfig.ts similarity index 75% rename from src/lib/readConfig.js rename to src/lib/readConfig.ts index b1829f9e..912edd66 100644 --- a/src/lib/readConfig.js +++ b/src/lib/readConfig.ts @@ -1,7 +1,8 @@ -const { existsSync, readFileSync } = require('fs'); -const { join } = require('path'); -const parse = require('@iarna/toml/parse-string.js'); -const Logger = require('./logger.js'); +import { existsSync, readFileSync } from 'fs'; +import { join } from 'path'; +import parse from '@iarna/toml/parse-string'; +import Logger from './logger'; +import { Config } from './types'; const e = (val, type, fn) => ({ val, type, fn }); @@ -14,9 +15,14 @@ const envValues = [ e('LOGGER', 'boolean', (c, v) => c.core.logger = v ?? true), e('STATS_INTERVAL', 'number', (c, v) => c.core.stats_interval = v), + e('DATASOURCE_TYPE', 'string', (c, v) => c.datasource.type = v), + e('DATASOURCE_LOCAL_DIRECTORY', 'string', (c, v) => c.datasource.local.directory = v), + e('DATASOURCE_S3_ACCESS_KEY_ID', 'string', (c, v) => c.datasource.s3.access_key_id = v ), + e('DATASOURCE_S3_SECRET_ACCESS_KEY', 'string', (c, v) => c.datasource.s3.secret_access_key = v), + e('DATASOURCE_S3_BUCKET', 'string', (c, v) => c.datasource.s3.bucket = v), + e('UPLOADER_ROUTE', 'string', (c, v) => c.uploader.route = v), e('UPLOADER_LENGTH', 'number', (c, v) => c.uploader.length = v), - e('UPLOADER_DIRECTORY', 'string', (c, v) => c.uploader.directory = v), e('UPLOADER_ADMIN_LIMIT', 'number', (c, v) => c.uploader.admin_limit = v), e('UPLOADER_USER_LIMIT', 'number', (c, v) => c.uploader.user_limit = v), e('UPLOADER_DISABLED_EXTS', 'array', (c, v) => v ? c.uploader.disabled_extentions = v : c.uploader.disabled_extentions = []), @@ -28,7 +34,7 @@ const envValues = [ e('RATELIMIT_ADMIN', 'number', (c, v) => c.ratelimit.user = v ?? 0), ]; -module.exports = function readConfig() { +export default function readConfig(): Config { if (!existsSync(join(process.cwd(), 'config.toml'))) { if (!process.env.ZIPLINE_DOCKER_BUILD) Logger.get('config').info('reading environment'); return tryReadEnv(); @@ -43,7 +49,7 @@ module.exports = function readConfig() { } }; -function tryReadEnv() { +function tryReadEnv(): Config { const config = { core: { secure: undefined, @@ -54,10 +60,20 @@ function tryReadEnv() { logger: undefined, stats_interval: undefined, }, + datasource: { + type: undefined, + local: { + directory: undefined, + }, + s3: { + access_key_id: undefined, + secret_access_key: undefined, + bucket: undefined, + }, + }, uploader: { route: undefined, length: undefined, - directory: undefined, admin_limit: undefined, user_limit: undefined, disabled_extentions: undefined, @@ -74,7 +90,7 @@ function tryReadEnv() { for (let i = 0, L = envValues.length; i !== L; ++i) { const envValue = envValues[i]; - let value = process.env[envValue.val]; + let value: any = process.env[envValue.val]; if (!value) { envValues[i].fn(config, undefined); diff --git a/src/lib/types.ts b/src/lib/types.ts index 425e5af1..b098346c 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -21,6 +21,26 @@ export interface ConfigCore { stats_interval: number; } +export interface ConfigDatasource { + // The type of datasource + type: 'local' | 's3'; + + // The local datasource + local: ConfigLocalDatasource; + s3?: ConfigS3Datasource; +} + +export interface ConfigLocalDatasource { + // The directory to store files in + directory: string; +} + +export interface ConfigS3Datasource { + access_key_id: string; + secret_access_key: string; + bucket: string; +} + export interface ConfigUploader { // The route uploads will be served on route: string; @@ -28,9 +48,6 @@ export interface ConfigUploader { // Length of random chars to generate for file names length: number; - // Where uploads are stored - directory: string; - // Admin file upload limit admin_limit: number; @@ -63,4 +80,5 @@ export interface Config { uploader: ConfigUploader; urls: ConfigUrls; ratelimit: ConfigRatelimit; + datasource: ConfigDatasource; } diff --git a/src/pages/[...id].tsx b/src/pages/[...id].tsx index de596c77..d9e8240f 100644 --- a/src/pages/[...id].tsx +++ b/src/pages/[...id].tsx @@ -1,14 +1,11 @@ import React, { useEffect } from 'react'; import Head from 'next/head'; import { GetServerSideProps } from 'next'; -import { Box, useMantineTheme } from '@mantine/core'; +import { Box } from '@mantine/core'; import config from 'lib/config'; import prisma from 'lib/prisma'; -import { getFile } from '../../server/util'; import { parse } from 'lib/clientUtils'; import * as exts from '../../scripts/exts'; -import { Prism } from '@mantine/prism'; -import ZiplineTheming from 'components/Theming'; export default function EmbeddedImage({ image, user }) { const dataURL = (route: string) => `${route}/${image.file}`; @@ -120,8 +117,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { image.created_at = image.created_at.toString(); const prismRender = Object.keys(exts).includes(image.file.split('.').pop()); - // let prismRenderCode;/ - // if (prismRender) prismRenderCode = (await getFile(config.uploader.directory, id)).toString(); if (prismRender) return { redirect: { destination: `/code/${image.file}`, @@ -130,7 +125,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => { }; if (!image.mimetype.startsWith('image')) { - const data = await getFile(config.uploader.directory, id); + const { default: datasource } = await import('lib/ds'); + + const data = await datasource.get(image.file); if (!data) return { notFound: true }; context.res.end(data); diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts index 44b22289..134d2ef7 100644 --- a/src/pages/api/upload.ts +++ b/src/pages/api/upload.ts @@ -3,12 +3,11 @@ import prisma from 'lib/prisma'; import zconfig from 'lib/config'; import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline'; import { createInvisImage, randomChars } from 'lib/util'; -import { writeFile } from 'fs/promises'; -import { join } from 'path'; import Logger from 'lib/logger'; import { ImageFormat, InvisibleImage } from '@prisma/client'; import { format as formatDate } from 'fecha'; import { v4 } from 'uuid'; +import datasource from 'lib/ds'; const uploader = multer({ storage: multer.memoryStorage(), @@ -70,7 +69,7 @@ async function handler(req: NextApiReq, res: NextApiRes) { if (req.headers.zws) invis = await createInvisImage(zconfig.uploader.length, image.id); - await writeFile(join(process.cwd(), zconfig.uploader.directory, image.file), file.buffer); + await datasource.save(image.file, file.buffer); Logger.get('image').info(`User ${user.username} (${user.id}) uploaded an image ${image.file} (${image.id})`); files.push(`${zconfig.core.secure ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route}/${invis ? invis.invis : image.file}`); } diff --git a/src/pages/api/user/files.ts b/src/pages/api/user/files.ts index e779a7cf..04f9ca49 100644 --- a/src/pages/api/user/files.ts +++ b/src/pages/api/user/files.ts @@ -43,6 +43,9 @@ async function handler(req: NextApiReq, res: NextApiRes) { userId: user.id, favorite: !!req.query.favorite, }, + orderBy: { + created_at: 'desc', + }, select: { created_at: true, file: true, diff --git a/src/pages/dashboard/upload.tsx b/src/pages/dashboard/upload.tsx index f7689c5d..cb87dc58 100644 --- a/src/pages/dashboard/upload.tsx +++ b/src/pages/dashboard/upload.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { GetStaticProps } from 'next'; import useLogin from 'hooks/useLogin'; import Layout from 'components/Layout'; import Upload from 'components/pages/Upload'; -import config from 'lib/config'; export default function UploadPage({ route }) { const { user, loading } = useLogin(); @@ -14,17 +12,9 @@ export default function UploadPage({ route }) { - + ); } -export const getStaticProps: GetStaticProps = async () => { - return { - props: { - route: process.env.ZIPLINE_DOCKER_BUILD === '1' ? '/u' : config.uploader.route, - }, - }; -}; - UploadPage.title = 'Zipline - Upload'; \ No newline at end of file diff --git a/server/index.js b/src/server/index.ts similarity index 74% rename from server/index.js rename to src/server/index.ts index 0d3d541d..6fde30b8 100644 --- a/server/index.js +++ b/src/server/index.ts @@ -1,15 +1,15 @@ -const next = require('next').default; -const { createServer } = require('http'); -const { mkdir } = require('fs/promises'); -const { extname } = require('path'); -const validateConfig = require('./validateConfig'); -const Logger = require('../src/lib/logger'); -const readConfig = require('../src/lib/readConfig'); -const mimes = require('../scripts/mimes'); -const { log, getStats, getFile, migrations } = require('./util'); -const { PrismaClient } = require('@prisma/client'); -const { version } = require('../package.json'); -const exts = require('../scripts/exts'); +import next from 'next'; +import { createServer } from 'http'; +import { extname } from 'path'; +import Logger from '../lib/logger'; +import mimes from '../../scripts/mimes'; +import { log, getStats, migrations } from './util'; +import { PrismaClient } from '@prisma/client'; +import { version } from '../../package.json'; +import exts from '../../scripts/exts'; +import datasource from '../lib/ds'; +import config from '../lib/config'; +import { mkdir } from 'fs/promises'; const serverLog = Logger.get('server'); serverLog.info(`starting zipline@${version} server`); @@ -26,13 +26,12 @@ const dev = process.env.NODE_ENV === 'development'; })(); async function run() { - const a = readConfig(); - const config = validateConfig(a); - process.env.DATABASE_URL = config.core.database_url; await migrations(); - await mkdir(config.uploader.directory, { recursive: true }); + if (config.datasource.type === 'local') { + await mkdir(config.datasource.local.directory, { recursive: true }); + } const app = next({ dir: '.', @@ -68,14 +67,14 @@ async function run() { }); if (!image) { - const data = await getFile(config.uploader.directory, parts[2]); + const data = await datasource.get(parts[2]); if (!data) return app.render404(req, res); const mimetype = mimes[extname(parts[2])] ?? 'application/octet-stream'; res.setHeader('Content-Type', mimetype); res.end(data); } else { - const data = await getFile(config.uploader.directory, image.file); + const data = await datasource.get(parts[2]); if (!data) return app.render404(req, res); await prisma.image.update({ @@ -106,7 +105,7 @@ async function run() { }); if (!image) { - const data = await getFile(config.uploader.directory, parts[2]); + const data = await datasource.get(parts[2]); if (!data) return app.render404(req, res); const mimetype = mimes[extname(parts[2])] ?? 'application/octet-stream'; @@ -117,7 +116,8 @@ async function run() { } else { const ext = image.file.split('.').pop(); if (Object.keys(exts).includes(ext)) return handle(req, res); - const data = await getFile(config.uploader.directory, image.file); + + const data = await datasource.get(parts[2]); if (!data) return app.render404(req, res); await prisma.image.update({ @@ -131,7 +131,7 @@ async function run() { handle(req, res); } - if (config.core.logger) log(req.url, res.statusCode); + if (config.core.logger) log(req.url); }); srv.on('error', (e) => { @@ -146,14 +146,14 @@ async function run() { srv.listen(config.core.port, config.core.host ?? '0.0.0.0'); - const stats = await getStats(prisma, config); + const stats = await getStats(prisma, datasource); await prisma.stats.create({ data: { data: stats, }, }); setInterval(async () => { - const stats = await getStats(prisma, config); + const stats = await getStats(prisma, datasource); await prisma.stats.create({ data: { data: stats, diff --git a/server/util.js b/src/server/util.ts similarity index 74% rename from server/util.js rename to src/server/util.ts index 6f9ec638..f5f15b84 100644 --- a/server/util.js +++ b/src/server/util.ts @@ -1,9 +1,11 @@ -const { readFile, readdir, stat } = require('fs/promises'); -const { join } = require('path'); -const { Migrate } = require('@prisma/migrate/dist/Migrate.js'); -const Logger = require('../src/lib/logger.js'); +import { readFile, readdir, stat } from 'fs/promises'; +import { join } from 'path'; +import { Migrate } from '@prisma/migrate/dist/Migrate'; +import Logger from '../lib/logger'; +import { execSync } from 'child_process'; +import { Datasource } from '../lib/datasource/index'; -async function migrations() { +export async function migrations() { const migrate = new Migrate('./prisma/schema.prisma'); const diagnose = await migrate.diagnoseMigrationHistory({ optInToShadowDatabase: false, @@ -18,12 +20,12 @@ async function migrations() { migrate.stop(); } -function log(url) { +export function log(url) { if (url.startsWith('/_next') || url.startsWith('/__nextjs')) return; return Logger.get('url').info(url); } -function shouldUseYarn() { +export function shouldUseYarn() { try { execSync('yarnpkg --version', { stdio: 'ignore' }); return true; @@ -32,17 +34,7 @@ function shouldUseYarn() { } } - -async function getFile(dir, file) { - try { - const data = await readFile(join(process.cwd(), dir, file)); - return data; - } catch (e) { - return null; - } -} - -async function sizeOfDir(directory) { +export async function sizeOfDir(directory) { const files = await readdir(directory); let size = 0; @@ -54,7 +46,7 @@ async function sizeOfDir(directory) { return size; } -function bytesToRead(bytes) { +export function bytesToRead(bytes) { const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']; let num = 0; @@ -67,8 +59,8 @@ function bytesToRead(bytes) { } -async function getStats(prisma, config) { - const size = await sizeOfDir(join(process.cwd(), config.uploader.directory)); +export async function getStats(prisma, datasource: Datasource) { + const size = await datasource.size(); const byUser = await prisma.image.groupBy({ by: ['userId'], _count: { @@ -117,14 +109,4 @@ async function getStats(prisma, config) { views_count: (viewsCount[0]?._sum?.views ?? 0), types_count: types_count.sort((a,b) => b.count-a.count), }; -} - -module.exports = { - migrations, - bytesToRead, - getFile, - getStats, - log, - sizeOfDir, - shouldUseYarn, -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/server/validateConfig.ts b/src/server/validateConfig.ts new file mode 100644 index 00000000..4e38ea13 --- /dev/null +++ b/src/server/validateConfig.ts @@ -0,0 +1,60 @@ +import { Config } from 'lib/types'; +import { object, bool, string, number, boolean, array } from 'yup'; + +const validator = object({ + core: object({ + secure: bool().default(false), + secret: string().min(8).required(), + host: string().default('0.0.0.0'), + port: number().default(3000), + database_url: string().required(), + logger: boolean().default(false), + stats_interval: number().default(1800), + }).required(), + datasource: object({ + type: string().default('local'), + local: object({ + directory: string().default('./uploads'), + }), + s3: object({ + access_key_id: string(), + secret_access_key: string(), + bucket: string(), + }).notRequired(), + }).required(), + uploader: object({ + route: string().default('/u'), + embed_route: string().default('/a'), + length: number().default(6), + admin_limit: number().default(104900000), + user_limit: number().default(104900000), + disabled_extensions: array().default([]), + }).required(), + urls: object({ + route: string().default('/go'), + length: number().default(6), + }).required(), + ratelimit: object({ + user: number().default(0), + admin: number().default(0), + }), +}); + + +export default function validate(config): Config { + try { + const validated = validator.validateSync(config, { abortEarly: false }); + if (validated.datasource.type === 's3') { + const errors = []; + if (!validated.datasource.s3.access_key_id) errors.push('datasource.s3.access_key_id is a required field'); + if (!validated.datasource.s3.secret_access_key) errors.push('datasource.s3.secret_access_key is a required field'); + if (!validated.datasource.s3.bucket) errors.push('datasource.s3.bucket is a required field'); + if (errors.length) throw { errors }; + } + + return validated as unknown as Config; + } catch (e) { + if (process.env.ZIPLINE_DOCKER_BUILD) return null; + throw `${e.errors.length} errors occured\n${e.errors.map(x => '\t' + x).join('\n')}`; + } +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4869927b..1e8bbf78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1407,6 +1407,21 @@ attr-accept@^2.2.2: resolved "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz" integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== +aws-sdk@^2.1085.0: + version "2.1085.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1085.0.tgz#bb509d1e321754fbcd90dff3449f5601e64fb607" + integrity sha512-zL20v5QXoSsb2S6RKAhDmfnZYUIRldR93ihJ6mCYrw6Zl+Dir2SVmALy++U/zduDbKB4NaU72mscumx2RWc7Hg== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + axe-core@^4.0.2: version "4.2.2" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.2.2.tgz" @@ -1446,7 +1461,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1509,6 +1524,15 @@ buffer-writer@2.0.0: resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2056,6 +2080,126 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-android-arm64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz#c89b3c50b4f47668dcbeb0b34ee4615258818e71" + integrity sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw== + +esbuild-darwin-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz#1c131e8cb133ed935ca32f824349a117c896a15b" + integrity sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug== + +esbuild-darwin-arm64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz#3c6245a50109dd84953f53d7833bd3b4f0e8c6fa" + integrity sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw== + +esbuild-freebsd-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz#0cdc54e72d3dd9cd992f9c2960055e68a7f8650c" + integrity sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA== + +esbuild-freebsd-arm64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz#1d11faed3a0c429e99b7dddef84103eb509788b2" + integrity sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg== + +esbuild-linux-32@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz#fd9f033fc27dcab61100cb1eb1c936893a68c841" + integrity sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ== + +esbuild-linux-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz#c04c438514f1359ecb1529205d0c836d4165f198" + integrity sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ== + +esbuild-linux-arm64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz#d1b3ab2988ab0734886eb9e811726f7db099ab96" + integrity sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g== + +esbuild-linux-arm@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz#df7558b6a5076f5eb9fd387c8704f768b61d97fb" + integrity sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw== + +esbuild-linux-mips64le@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz#bb4c47fccc9493d460ffeb1f88e8a97a98a14f8b" + integrity sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw== + +esbuild-linux-ppc64le@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz#a332dbc8a1b4e30cfe1261bfaa5cef57c9c8c02a" + integrity sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag== + +esbuild-linux-riscv64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz#85675f3f931f5cd7cfb238fd82f77a62ffcb6d86" + integrity sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg== + +esbuild-linux-s390x@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz#a526282a696e6d846f4c628f5315475518c0c0f0" + integrity sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA== + +esbuild-netbsd-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz#8e456605694719aa1be4be266d6cd569c06dfaf5" + integrity sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g== + +esbuild-openbsd-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz#f2fc51714b4ddabc86e4eb30ca101dd325db2f7d" + integrity sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA== + +esbuild-sunos-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz#a408f33ea20e215909e20173a0fd78b1aaad1f8e" + integrity sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g== + +esbuild-windows-32@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz#b9005bbff54dac3975ff355d5de2b5e37165d128" + integrity sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA== + +esbuild-windows-64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz#2b5a99befeaca6aefdad32d738b945730a60a060" + integrity sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g== + +esbuild-windows-arm64@0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz#edc560bbadb097eb45fc235aeacb942cb94a38c0" + integrity sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw== + +esbuild@^0.14.23: + version "0.14.23" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.23.tgz#95e842cb22bc0c7d82c140adc16788aac91469fe" + integrity sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig== + optionalDependencies: + esbuild-android-arm64 "0.14.23" + esbuild-darwin-64 "0.14.23" + esbuild-darwin-arm64 "0.14.23" + esbuild-freebsd-64 "0.14.23" + esbuild-freebsd-arm64 "0.14.23" + esbuild-linux-32 "0.14.23" + esbuild-linux-64 "0.14.23" + esbuild-linux-arm "0.14.23" + esbuild-linux-arm64 "0.14.23" + esbuild-linux-mips64le "0.14.23" + esbuild-linux-ppc64le "0.14.23" + esbuild-linux-riscv64 "0.14.23" + esbuild-linux-s390x "0.14.23" + esbuild-netbsd-64 "0.14.23" + esbuild-openbsd-64 "0.14.23" + esbuild-sunos-64 "0.14.23" + esbuild-windows-32 "0.14.23" + esbuild-windows-64 "0.14.23" + esbuild-windows-arm64 "0.14.23" + escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" @@ -2276,6 +2420,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -2738,7 +2887,12 @@ iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -2957,7 +3111,7 @@ isarray@0.0.1: resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2967,6 +3121,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -4079,11 +4238,21 @@ psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -4393,6 +4562,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o= + sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -5083,6 +5257,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + use-composed-ref@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" @@ -5112,6 +5294,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -5206,6 +5393,14 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +xml2js@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + xml2js@^0.4.19: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" @@ -5219,6 +5414,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + xtend@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" diff --git a/zip-env.d.ts b/zip-env.d.ts index 3fbf3dce..1c7a6dac 100644 --- a/zip-env.d.ts +++ b/zip-env.d.ts @@ -1,11 +1,13 @@ import type { PrismaClient } from '@prisma/client'; -import type { Config } from './src/lib/types'; +import type { Datasource } from 'lib/datasource'; +import type { Config } from '.lib/types'; declare global { namespace NodeJS { interface Global { prisma: PrismaClient; config: Config; + datasource: Datasource } interface ProcessEnv {