feat(v3.4.3): cleanup, fix memory leak, arm support
This commit is contained in:
parent
083040e300
commit
aa611fa6ba
18 changed files with 315 additions and 241 deletions
|
@ -1,4 +1,4 @@
|
|||
FROM node:16-alpine AS deps
|
||||
FROM node:17-alpine AS deps
|
||||
WORKDIR /build
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
@ -6,7 +6,7 @@ COPY package.json yarn.lock ./
|
|||
RUN apk add --no-cache libc6-compat
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
FROM node:16-alpine AS builder
|
||||
FROM node:17-alpine AS builder
|
||||
WORKDIR /build
|
||||
|
||||
COPY --from=deps /build/node_modules ./node_modules
|
||||
|
@ -20,7 +20,7 @@ ENV NEXT_TELEMETRY_DISABLED 1
|
|||
|
||||
RUN yarn build
|
||||
|
||||
FROM node:16-alpine AS runner
|
||||
FROM node:17-alpine AS runner
|
||||
WORKDIR /zipline
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
|
44
Dockerfile-arm
Normal file
44
Dockerfile-arm
Normal file
|
@ -0,0 +1,44 @@
|
|||
FROM node:17 AS deps
|
||||
WORKDIR /build
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
FROM node:17 AS builder
|
||||
WORKDIR /build
|
||||
|
||||
COPY --from=deps /build/node_modules ./node_modules
|
||||
COPY src ./src
|
||||
COPY scripts ./scripts
|
||||
COPY prisma ./prisma
|
||||
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
|
||||
|
||||
RUN yarn build
|
||||
|
||||
FROM node:17 AS runner
|
||||
WORKDIR /zipline
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
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/scripts ./scripts
|
||||
COPY --from=builder /build/prisma ./prisma
|
||||
COPY --from=builder /build/tsconfig.json ./tsconfig.json
|
||||
COPY --from=builder /build/package.json ./package.json
|
||||
|
||||
USER zipline
|
||||
|
||||
CMD ["node", "dist/server"]
|
|
@ -1,8 +1,11 @@
|
|||
const esbuild = require('esbuild');
|
||||
const { rm } = require('fs/promises');
|
||||
|
||||
(async () => {
|
||||
const watch = process.argv[2] === '--watch';
|
||||
|
||||
await rm('./dist', { recursive: true });
|
||||
|
||||
await esbuild.build({
|
||||
tsconfig: 'tsconfig.json',
|
||||
outdir: 'dist',
|
||||
|
@ -11,6 +14,7 @@ const esbuild = require('esbuild');
|
|||
treeShaking: true,
|
||||
entryPoints: [
|
||||
'src/server/index.ts',
|
||||
'src/server/server.ts',
|
||||
'src/server/util.ts',
|
||||
'src/server/validateConfig.ts',
|
||||
'src/lib/logger.ts',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "zip3",
|
||||
"version": "3.4.2",
|
||||
"version": "3.4.3",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.js && REACT_EDITOR=code-insiders NODE_ENV=development node dist/server",
|
||||
|
@ -35,7 +35,8 @@
|
|||
"colorette": "^1.2.2",
|
||||
"cookie": "^0.4.1",
|
||||
"fecha": "^4.2.1",
|
||||
"multer": "^1.4.2",
|
||||
"find-my-way": "^5.2.0",
|
||||
"multer": "^1.4.4",
|
||||
"next": "^12.1.0",
|
||||
"prisma": "^3.9.2",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { readdir, readFile, stat, writeFile } from 'fs/promises';
|
||||
import { createReadStream, ReadStream } from 'fs';
|
||||
import { readdir, stat, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { Datasource } from './datasource';
|
||||
|
||||
|
@ -13,10 +14,9 @@ export class Local extends Datasource {
|
|||
await writeFile(join(process.cwd(), this.path, file), data);
|
||||
}
|
||||
|
||||
public async get(file: string): Promise<Buffer> {
|
||||
public get(file: string): ReadStream {
|
||||
try {
|
||||
const data = await readFile(join(process.cwd(), this.path, file));
|
||||
return data;
|
||||
return createReadStream(join(process.cwd(), this.path, file));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Datasource } from './datasource';
|
||||
import AWS from 'aws-sdk';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export class S3 extends Datasource {
|
||||
public name: string = 'S3';
|
||||
|
@ -33,20 +34,12 @@ export class S3 extends Datasource {
|
|||
});
|
||||
}
|
||||
|
||||
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 get(file: string): Readable {
|
||||
// Unfortunately, aws-sdk is bad and the stream still loads everything into memory.
|
||||
return this.s3.getObject({
|
||||
Bucket: this.bucket,
|
||||
Key: file,
|
||||
}).createReadStream();
|
||||
}
|
||||
|
||||
public async size(): Promise<number> {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { Readable } from 'stream';
|
||||
|
||||
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 get(file: string): Readable;
|
||||
public abstract size(): Promise<number>;
|
||||
}
|
|
@ -5,16 +5,16 @@ import Logger from './logger';
|
|||
if (!global.datasource) {
|
||||
switch (config.datasource.type) {
|
||||
case 's3':
|
||||
Logger.get('datasource').info(`Using S3(${config.datasource.s3.bucket}) datasource`);
|
||||
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':
|
||||
Logger.get('datasource').info(`Using local(${config.datasource.local.directory}) datasource`);
|
||||
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;
|
|
@ -21,12 +21,12 @@ export default class Logger {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
console.log(this.formatMessage(LoggerLevel.INFO, this.name, message));
|
||||
info(...args) {
|
||||
console.log(this.formatMessage(LoggerLevel.INFO, this.name, args.join(' ')));
|
||||
}
|
||||
|
||||
error(error: any) {
|
||||
console.log(this.formatMessage(LoggerLevel.ERROR, this.name, error.stack ?? error));
|
||||
error(...args: any[]) {
|
||||
console.log(this.formatMessage(LoggerLevel.ERROR, this.name, args.map(error => error.stack ?? error).join(' ')));
|
||||
}
|
||||
|
||||
formatMessage(level: LoggerLevel, name, message) {
|
||||
|
|
|
@ -127,10 +127,10 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||
if (!image.mimetype.startsWith('image')) {
|
||||
const { default: datasource } = await import('lib/ds');
|
||||
|
||||
const data = await datasource.get(image.file);
|
||||
const data = datasource.get(image.file);
|
||||
if (!data) return { notFound: true };
|
||||
|
||||
context.res.end(data);
|
||||
data.pipe(context.res);
|
||||
return { props: {} };
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ import { format as formatDate } from 'fecha';
|
|||
import { v4 } from 'uuid';
|
||||
import datasource from 'lib/ds';
|
||||
|
||||
const uploader = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
});
|
||||
const uploader = multer();
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.forbid('invalid method');
|
||||
|
@ -25,13 +23,16 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
if (!user) return res.forbid('authorization incorect');
|
||||
if (user.ratelimited) return res.ratelimited();
|
||||
|
||||
await run(uploader.array('file'))(req, res);
|
||||
|
||||
if (!req.files) return res.error('no files');
|
||||
|
||||
if (req.files && req.files.length === 0) return res.error('no files');
|
||||
|
||||
const rawFormat = ((req.headers.format || '') as string).toUpperCase() || 'RANDOM';
|
||||
const format: ImageFormat = Object.keys(ImageFormat).includes(rawFormat) && ImageFormat[rawFormat];
|
||||
const files = [];
|
||||
|
||||
for (let i = 0; i !== req.files.length; ++i) {
|
||||
const file = req.files[i];
|
||||
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit']) return res.error(`file[${i}] size too big`);
|
||||
|
@ -116,9 +117,7 @@ function run(middleware: any) {
|
|||
});
|
||||
}
|
||||
|
||||
export default async function handlers(req, res) {
|
||||
await run(uploader.array('file'))(req, res);
|
||||
|
||||
export default async function handlers(req, res) {
|
||||
return withZipline(handler)(req, res);
|
||||
};
|
||||
|
||||
|
|
|
@ -36,14 +36,14 @@ export default function Login() {
|
|||
icon: <Cross1Icon />,
|
||||
});
|
||||
} else {
|
||||
router.push(router.query.url as string || '/dashboard');
|
||||
await router.push(router.query.url as string || '/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const a = await fetch('/api/user');
|
||||
if (a.ok) router.push('/dashboard');
|
||||
if (a.ok) await router.push('/dashboard');
|
||||
else {
|
||||
const v = await useFetch('/api/version');
|
||||
setVersions(v);
|
||||
|
|
|
@ -4,8 +4,6 @@ import { LoadingOverlay } from '@mantine/core';
|
|||
|
||||
export default function Logout() {
|
||||
const router = useRouter();
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
@ -20,7 +18,7 @@ export default function Logout() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<LoadingOverlay visible={visible} />
|
||||
<LoadingOverlay visible={true} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function Code() {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await fetch('/r/' + id);
|
||||
if (id && !res.ok) router.push('/404');
|
||||
if (id && !res.ok) await router.push('/404');
|
||||
const data = await res.text();
|
||||
if (id) setPrismRenderCode(data);
|
||||
})();
|
||||
|
|
|
@ -1,164 +1,3 @@
|
|||
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');
|
||||
import Server from './server';
|
||||
|
||||
serverLog.info(`starting zipline@${version} server`);
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await run();
|
||||
} catch (e) {
|
||||
serverLog.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
async function run() {
|
||||
process.env.DATABASE_URL = config.core.database_url;
|
||||
await migrations();
|
||||
|
||||
if (config.datasource.type === 'local') {
|
||||
await mkdir(config.datasource.local.directory, { recursive: true });
|
||||
}
|
||||
|
||||
const app = next({
|
||||
dir: '.',
|
||||
dev,
|
||||
quiet: !dev,
|
||||
hostname: config.core.host,
|
||||
port: config.core.port,
|
||||
});
|
||||
|
||||
await app.prepare();
|
||||
|
||||
const handle = app.getRequestHandler();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const srv = createServer(async (req, res) => {
|
||||
if (req.url.startsWith('/r')) {
|
||||
const parts = req.url.split('/');
|
||||
if (!parts[2] || parts[2] === '') return;
|
||||
|
||||
let image = await prisma.image.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ file: parts[2] },
|
||||
{ invisible:{ invis: decodeURI(parts[2]) } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
mimetype: true,
|
||||
id: true,
|
||||
file: true,
|
||||
invisible: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!image) {
|
||||
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 datasource.get(parts[2]);
|
||||
if (!data) return app.render404(req, res);
|
||||
|
||||
await prisma.image.update({
|
||||
where: { id: image.id },
|
||||
data: { views: { increment: 1 } },
|
||||
});
|
||||
res.setHeader('Content-Type', image.mimetype);
|
||||
res.end(data);
|
||||
}
|
||||
} else if (req.url.startsWith(config.uploader.route)) {
|
||||
const parts = req.url.split('/');
|
||||
if (!parts[2] || parts[2] === '') return;
|
||||
|
||||
let image = await prisma.image.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ file: parts[2] },
|
||||
{ invisible:{ invis: decodeURI(parts[2]) } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
mimetype: true,
|
||||
id: true,
|
||||
file: true,
|
||||
invisible: true,
|
||||
embed: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!image) {
|
||||
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 if (image.embed) {
|
||||
handle(req, res);
|
||||
} else {
|
||||
const ext = image.file.split('.').pop();
|
||||
if (Object.keys(exts).includes(ext)) return handle(req, res);
|
||||
|
||||
const data = await datasource.get(parts[2]);
|
||||
if (!data) return app.render404(req, res);
|
||||
|
||||
await prisma.image.update({
|
||||
where: { id: image.id },
|
||||
data: { views: { increment: 1 } },
|
||||
});
|
||||
res.setHeader('Content-Type', image.mimetype);
|
||||
res.end(data);
|
||||
}
|
||||
} else {
|
||||
handle(req, res);
|
||||
}
|
||||
|
||||
if (config.core.logger) log(req.url);
|
||||
});
|
||||
|
||||
srv.on('error', (e) => {
|
||||
serverLog.error(e);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
srv.on('listening', () => {
|
||||
serverLog.info(`listening on ${config.core.host}:${config.core.port}`);
|
||||
});
|
||||
|
||||
srv.listen(config.core.port, config.core.host ?? '0.0.0.0');
|
||||
|
||||
const stats = await getStats(prisma, datasource);
|
||||
await prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
setInterval(async () => {
|
||||
const stats = await getStats(prisma, datasource);
|
||||
await prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
if (config.core.logger) serverLog.info('stats updated');
|
||||
}, config.core.stats_interval * 1000);
|
||||
}
|
||||
new Server();
|
186
src/server/server.ts
Normal file
186
src/server/server.ts
Normal file
|
@ -0,0 +1,186 @@
|
|||
import Router from 'find-my-way';
|
||||
import { NextServer, RequestHandler } from 'next/dist/server/next';
|
||||
import { Image, PrismaClient } from '@prisma/client';
|
||||
import { createServer, IncomingMessage, OutgoingMessage, Server as HttpServer, ServerResponse } from 'http';
|
||||
import next from 'next';
|
||||
import config from '../lib/config';
|
||||
import datasource from '../lib/ds';
|
||||
import { getStats, log, migrations } from './util';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import Logger from '../lib/logger';
|
||||
import mimes from '../../scripts/mimes';
|
||||
import { extname } from 'path';
|
||||
import exts from '../../scripts/exts';
|
||||
import { version } from '../../package.json';
|
||||
|
||||
const serverLog = Logger.get('server');
|
||||
|
||||
export default class Server {
|
||||
public router: Router.Instance<Router.HTTPVersion.V1>;
|
||||
public nextServer: NextServer;
|
||||
public handle: RequestHandler;
|
||||
public prisma: PrismaClient;
|
||||
|
||||
private http: HttpServer;
|
||||
|
||||
public constructor() {
|
||||
serverLog.info(`starting zipline@${version} server`);
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
private async start() {
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
|
||||
process.env.DATABASE_URL = config.core.database_url;
|
||||
await migrations();
|
||||
|
||||
this.prisma = new PrismaClient();
|
||||
|
||||
if (config.datasource.type === 'local') {
|
||||
await mkdir(config.datasource.local.directory, { recursive: true });
|
||||
}
|
||||
|
||||
this.nextServer = next({
|
||||
dir: '.',
|
||||
dev,
|
||||
quiet: !dev,
|
||||
hostname: config.core.host,
|
||||
port: config.core.port,
|
||||
});
|
||||
|
||||
this.handle = this.nextServer.getRequestHandler();
|
||||
this.router = Router({
|
||||
defaultRoute: (req, res) => {
|
||||
this.handle(req, res);
|
||||
},
|
||||
});
|
||||
|
||||
this.router.on('GET', `${config.uploader.route}/:id`, async (req, res, params) => {
|
||||
const image = await this.prisma.image.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ file: params.id },
|
||||
{ invisible: { invis: decodeURI(params.id) } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
mimetype: true,
|
||||
id: true,
|
||||
file: true,
|
||||
invisible: true,
|
||||
embed: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!image) await this.rawFile(req, res, params.id);
|
||||
else if (image.embed) await this.handle(req, res);
|
||||
else await this.fileDb(req, res, image);
|
||||
});
|
||||
|
||||
this.router.on('GET', '/r/:id', async (req, res, params) => {
|
||||
const image = await this.prisma.image.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ file: params.id },
|
||||
{ invisible: { invis: decodeURI(params.id) } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
mimetype: true,
|
||||
id: true,
|
||||
file: true,
|
||||
invisible: true,
|
||||
embed: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!image) await this.rawFile(req, res, params.id);
|
||||
else await this.rawFileDb(req, res, image);
|
||||
});
|
||||
|
||||
await this.nextServer.prepare();
|
||||
|
||||
this.http = createServer((req, res) => {
|
||||
this.router.lookup(req, res);
|
||||
if (config.core.logger) log(req.url);
|
||||
});
|
||||
|
||||
this.http.on('error', (e) => {
|
||||
serverLog.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
this.http.on('listening', () => {
|
||||
serverLog.info(`listening on ${config.core.host}:${config.core.port}`);
|
||||
});
|
||||
|
||||
this.http.listen(config.core.port, config.core.host ?? '0.0.0.0');
|
||||
|
||||
this.stats();
|
||||
}
|
||||
|
||||
private async rawFile(req: IncomingMessage, res: OutgoingMessage, id: string) {
|
||||
const data = datasource.get(id);
|
||||
if (!data) return this.nextServer.render404(req, res as ServerResponse);
|
||||
|
||||
const mimetype = mimes[extname(id)] ?? 'application/octet-stream';
|
||||
res.setHeader('Content-Type', mimetype);
|
||||
|
||||
data.pipe(res);
|
||||
data.on('error', () => this.nextServer.render404(req, res as ServerResponse));
|
||||
data.on('end', () => res.end());
|
||||
}
|
||||
|
||||
private async rawFileDb(req: IncomingMessage, res: OutgoingMessage, image: any) {
|
||||
const data = datasource.get(image.file);
|
||||
if (!data) return this.nextServer.render404(req, res as ServerResponse);
|
||||
|
||||
res.setHeader('Content-Type', image.mimetype);
|
||||
data.pipe(res);
|
||||
data.on('error', () => this.nextServer.render404(req, res as ServerResponse));
|
||||
data.on('end', () => res.end());
|
||||
|
||||
await this.prisma.image.update({
|
||||
where: { id: image.id },
|
||||
data: { views: { increment: 1 } },
|
||||
});
|
||||
}
|
||||
|
||||
private async fileDb(req: IncomingMessage, res: OutgoingMessage, image: any) {
|
||||
const ext = image.file.split('.').pop();
|
||||
if (Object.keys(exts).includes(ext)) return this.handle(req, res as ServerResponse);
|
||||
|
||||
const data = datasource.get(image.file);
|
||||
if (!data) return this.nextServer.render404(req, res as ServerResponse);
|
||||
|
||||
res.setHeader('Content-Type', image.mimetype);
|
||||
data.pipe(res);
|
||||
data.on('error', () => this.nextServer.render404(req, res as ServerResponse));
|
||||
data.on('end', () => res.end());
|
||||
|
||||
await this.prisma.image.update({
|
||||
where: { id: image.id },
|
||||
data: { views: { increment: 1 } },
|
||||
});
|
||||
}
|
||||
|
||||
private async stats() {
|
||||
const stats = await getStats(this.prisma, datasource);
|
||||
await this.prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
|
||||
setInterval(async () => {
|
||||
const stats = await getStats(this.prisma, datasource);
|
||||
await this.prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
if (config.core.logger) serverLog.info('stats updated');
|
||||
}, config.core.stats_interval * 1000);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
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';
|
||||
import { Datasource } from 'lib/datasource';
|
||||
|
||||
export async function migrations() {
|
||||
const migrate = new Migrate('./prisma/schema.prisma');
|
||||
|
@ -25,27 +22,6 @@ export function log(url) {
|
|||
return Logger.get('url').info(url);
|
||||
}
|
||||
|
||||
export function shouldUseYarn() {
|
||||
try {
|
||||
execSync('yarnpkg --version', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sizeOfDir(directory) {
|
||||
const files = await readdir(directory);
|
||||
|
||||
let size = 0;
|
||||
for (let i = 0, L = files.length; i !== L; ++i) {
|
||||
const sta = await stat(join(directory, files[i]));
|
||||
size += sta.size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
export function bytesToRead(bytes) {
|
||||
const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
|
||||
let num = 0;
|
||||
|
|
46
yarn.lock
46
yarn.lock
|
@ -2450,6 +2450,11 @@ exit-on-epipe@~1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
|
||||
integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
|
||||
|
||||
fast-decode-uri-component@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543"
|
||||
integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==
|
||||
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||
|
@ -2535,6 +2540,16 @@ find-cache-dir@3.3.2:
|
|||
make-dir "^3.0.2"
|
||||
pkg-dir "^4.1.0"
|
||||
|
||||
find-my-way@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-5.2.0.tgz#3e8b6d55471c9eac52164d268db108ff64849ae0"
|
||||
integrity sha512-YLocRSSJJ1PCnrupBmaw2pUcFkU125QTWfFfpdLq11h7bQ+r+Ke4D1/4v7UkERlw0037VkYMd+1RMGTbhFCbPw==
|
||||
dependencies:
|
||||
fast-decode-uri-component "^1.0.1"
|
||||
fast-deep-equal "^3.1.3"
|
||||
safe-regex2 "^2.0.0"
|
||||
semver-store "^0.3.0"
|
||||
|
||||
find-root@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz"
|
||||
|
@ -3546,9 +3561,9 @@ minizlib@^2.1.1:
|
|||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
mkdirp@^0.5.4:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
@ -3596,15 +3611,15 @@ mssql@8.0.1:
|
|||
tarn "^3.0.2"
|
||||
tedious "^14.0.0"
|
||||
|
||||
multer@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz"
|
||||
integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
|
||||
multer@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c"
|
||||
integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==
|
||||
dependencies:
|
||||
append-field "^1.0.0"
|
||||
busboy "^0.2.11"
|
||||
concat-stream "^1.5.2"
|
||||
mkdirp "^0.5.1"
|
||||
mkdirp "^0.5.4"
|
||||
object-assign "^4.1.1"
|
||||
on-finished "^2.3.0"
|
||||
type-is "^1.6.4"
|
||||
|
@ -4511,6 +4526,11 @@ restore-cursor@^3.1.0:
|
|||
onetime "^5.1.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
ret@~0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c"
|
||||
integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==
|
||||
|
||||
retry@^0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
|
||||
|
@ -4557,6 +4577,13 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-regex2@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9"
|
||||
integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==
|
||||
dependencies:
|
||||
ret "~0.2.0"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||
|
@ -4585,6 +4612,11 @@ semver-compare@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
||||
|
||||
semver-store@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9"
|
||||
integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
|
||||
|
|
Loading…
Reference in a new issue