mirror of
https://github.com/diced/zipline.git
synced 2025-04-04 23:21:17 -05:00
feat(v3.4.1): datasource api, for S3 functionality
This commit is contained in:
parent
870f6e88b1
commit
99e92e4594
34 changed files with 606 additions and 240 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -36,4 +36,6 @@ yarn-error.log*
|
|||
|
||||
# zipline
|
||||
config.toml
|
||||
uploads/
|
||||
uploads/
|
||||
dist/
|
||||
docker-compose.local.yml
|
|
@ -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"]
|
||||
CMD ["node", "dist/server"]
|
2
LICENSE
2
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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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=
|
||||
|
|
33
esbuild.config.js
Normal file
33
esbuild.config.js
Normal file
|
@ -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',
|
||||
});
|
||||
})();
|
|
@ -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",
|
||||
|
|
|
@ -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')}`;
|
||||
}
|
||||
};
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from 'react-table';
|
||||
import {
|
||||
ActionIcon,
|
||||
Checkbox,
|
||||
createStyles,
|
||||
Divider,
|
||||
Group,
|
||||
|
|
6
src/components/StatText.tsx
Normal file
6
src/components/StatText.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Text } from '@mantine/core';
|
||||
|
||||
export default function StatText({ children }) {
|
||||
return <Text color='gray' size='xl'>{children}</Text>;
|
||||
}
|
|
@ -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 <Text color='gray' size='xl'>{children}</Text>;
|
||||
}
|
||||
|
||||
function StatTable({ rows, columns }) {
|
||||
return (
|
||||
<Box sx={{ pt: 1 }}>
|
||||
<Table highlightOnHover>
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map(col => (
|
||||
<th key={randomId()}>{col.name}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map(row => (
|
||||
<tr key={randomId()}>
|
||||
{columns.map(col => (
|
||||
<td key={randomId()}>
|
||||
{col.format ? col.format(row[col.id]) : row[col.id]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
<Image key={randomId()} image={image} updateImages={updateImages} />
|
||||
<ZiplineImage key={randomId()} image={image} updateImages={updateImages} />
|
||||
)) : [1,2,3,4].map(x => (
|
||||
<div key={x}>
|
||||
<Skeleton width='100%' height={220} sx={{ borderRadius: 1 }}/>
|
||||
|
|
|
@ -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 <Text color='gray' size='xl'>{children}</Text>;
|
||||
}
|
||||
|
||||
function StatTable({ rows, columns }) {
|
||||
return (
|
||||
<Box sx={{ pt: 1 }}>
|
||||
|
@ -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 () => {
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<Dropzone
|
||||
onDrop={(f) => setFiles([...files, ...f])}
|
||||
>
|
||||
{(status) => (
|
||||
<Dropzone onDrop={(f) => setFiles([...files, ...f])}>
|
||||
{status => (
|
||||
<>
|
||||
<Group position='center' spacing='xl' style={{ minHeight: 220, pointerEvents: 'none' }}>
|
||||
<ImageUploadIcon
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useStoreSelector } from 'lib/redux/store';
|
|||
import useFetch from 'hooks/useFetch';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import { Avatar, Modal, Title, TextInput, Group, Button, Card, Grid, ActionIcon, SimpleGrid, Switch, Skeleton } from '@mantine/core';
|
||||
import { Avatar, Modal, Title, TextInput, Group, Button, Card, ActionIcon, SimpleGrid, Switch, Skeleton } from '@mantine/core';
|
||||
import { Cross1Icon, PlusIcon, TrashIcon } from '@modulz/radix-icons';
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Config } from './types';
|
||||
import readConfig from './readConfig';
|
||||
import validateConfig from '../../server/validateConfig';
|
||||
import validateConfig from '../server/validateConfig';
|
||||
|
||||
if (!global.config) global.config = validateConfig(readConfig()) as unknown as Config;
|
||||
|
||||
|
|
36
src/lib/datasource/Local.ts
Normal file
36
src/lib/datasource/Local.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { readdir, readFile, stat, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { Datasource } from './datasource';
|
||||
|
||||
export class Local extends Datasource {
|
||||
public name: string = 'local';
|
||||
|
||||
public constructor(public path: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async save(file: string, data: Buffer): Promise<void> {
|
||||
await writeFile(join(process.cwd(), this.path, file), data);
|
||||
}
|
||||
|
||||
public async get(file: string): Promise<Buffer> {
|
||||
try {
|
||||
const data = await readFile(join(process.cwd(), this.path, file));
|
||||
return data;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async size(): Promise<number> {
|
||||
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;
|
||||
}
|
||||
}
|
66
src/lib/datasource/S3.ts
Normal file
66
src/lib/datasource/S3.ts
Normal file
|
@ -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<void> {
|
||||
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<Buffer> {
|
||||
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<number> {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
7
src/lib/datasource/datasource.ts
Normal file
7
src/lib/datasource/datasource.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export abstract class Datasource {
|
||||
public name: string;
|
||||
|
||||
public abstract save(file: string, data: Buffer): Promise<void>;
|
||||
public abstract get(file: string): Promise<Buffer>;
|
||||
public abstract size(): Promise<number>;
|
||||
}
|
4
src/lib/datasource/index.ts
Normal file
4
src/lib/datasource/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export { Datasource } from './datasource';
|
||||
export { Local } from './Local';
|
||||
export { S3 } from './S3';
|
||||
|
20
src/lib/ds.ts
Normal file
20
src/lib/ds.ts
Normal file
|
@ -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;
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
45
src/lib/logger.ts
Normal file
45
src/lib/logger.ts
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
|
@ -11,7 +11,7 @@ export interface NextApiFile {
|
|||
originalname: string;
|
||||
encoding: string;
|
||||
mimetype: string;
|
||||
buffer: string;
|
||||
buffer: Buffer;
|
||||
size: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }) {
|
|||
<Layout
|
||||
user={user}
|
||||
>
|
||||
<Upload route={route}/>
|
||||
<Upload/>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async () => {
|
||||
return {
|
||||
props: {
|
||||
route: process.env.ZIPLINE_DOCKER_BUILD === '1' ? '/u' : config.uploader.route,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
UploadPage.title = 'Zipline - Upload';
|
|
@ -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,
|
|
@ -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,
|
||||
};
|
||||
}
|
60
src/server/validateConfig.ts
Normal file
60
src/server/validateConfig.ts
Normal file
|
@ -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')}`;
|
||||
}
|
||||
};
|
206
yarn.lock
206
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"
|
||||
|
|
4
zip-env.d.ts
vendored
4
zip-env.d.ts
vendored
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue