refactor(server): clean up server code
This commit is contained in:
parent
4442c85dc1
commit
195c57edc3
3 changed files with 190 additions and 189 deletions
|
@ -17,7 +17,6 @@ const { rm } = require('fs/promises');
|
||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
'src/server/index.ts',
|
'src/server/index.ts',
|
||||||
'src/server/server.ts',
|
|
||||||
'src/server/util.ts',
|
'src/server/util.ts',
|
||||||
'src/lib/logger.ts',
|
'src/lib/logger.ts',
|
||||||
'src/lib/config.ts',
|
'src/lib/config.ts',
|
||||||
|
@ -36,6 +35,6 @@ const { rm } = require('fs/promises');
|
||||||
watch,
|
watch,
|
||||||
incremental: watch,
|
incremental: watch,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
minify: process.env.NODE_ENV === 'production',
|
minify: true,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
|
@ -1,7 +1,192 @@
|
||||||
import { version } from '../../package.json';
|
import Router from 'find-my-way';
|
||||||
|
import next from 'next';
|
||||||
|
import { NextServer, RequestHandler } from 'next/dist/server/next';
|
||||||
|
import { Image, PrismaClient } from '@prisma/client';
|
||||||
|
import { createServer, IncomingMessage, OutgoingMessage, ServerResponse } from 'http';
|
||||||
|
import { extname } from 'path';
|
||||||
|
import { mkdir } from 'fs/promises';
|
||||||
|
import { getStats, log, migrations } from './util';
|
||||||
import Logger from '../lib/logger';
|
import Logger from '../lib/logger';
|
||||||
|
import mimes from '../../scripts/mimes';
|
||||||
|
import exts from '../../scripts/exts';
|
||||||
|
import { version } from '../../package.json';
|
||||||
|
import config from '../lib/config';
|
||||||
|
import datasource from '../lib/datasource';
|
||||||
|
|
||||||
Logger.get('server').info(`starting zipline@${version} server`);
|
const logger = Logger.get('server');
|
||||||
|
logger.info(`starting zipline@${version} server`);
|
||||||
|
|
||||||
import Server from './server';
|
start();
|
||||||
new Server();
|
|
||||||
|
async function start() {
|
||||||
|
// annoy user if they didnt change secret from default "changethis"
|
||||||
|
if (config.core.secret === 'changethis') {
|
||||||
|
logger.error('Secret is not set!');
|
||||||
|
logger.error('Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!');
|
||||||
|
logger.error('Please change your secret in the config file or environment variables.');
|
||||||
|
logger.error('The config file is located at `config.toml`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.');
|
||||||
|
logger.error('It is recomended to use a secret that is alphanumeric and randomized. A way you can generate this is through a password manager you may have.');
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
process.env.DATABASE_URL = config.core.database_url;
|
||||||
|
await migrations();
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
if (config.datasource.type === 'local') {
|
||||||
|
await mkdir(config.datasource.local.directory, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextServer = next({
|
||||||
|
dir: '.',
|
||||||
|
dev,
|
||||||
|
quiet: !dev,
|
||||||
|
hostname: config.core.host,
|
||||||
|
port: config.core.port,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handle = nextServer.getRequestHandler();
|
||||||
|
const router = Router({
|
||||||
|
defaultRoute: (req, res) => {
|
||||||
|
handle(req, res);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
router.on('GET', config.uploader.route === '/' ? '/:id(^[^\\.]+\\.[^\\.]+)' : `${config.uploader.route}/:id`, async (req, res, params) => {
|
||||||
|
const image = await prisma.image.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ file: params.id },
|
||||||
|
{ invisible: { invis: decodeURI(params.id) } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(image);
|
||||||
|
|
||||||
|
if (!image) await rawFile(req, res, nextServer, params.id);
|
||||||
|
|
||||||
|
if (image.password) await handle(req, res);
|
||||||
|
else if (image.embed) await handle(req, res);
|
||||||
|
else await fileDb(req, res, nextServer, prisma, handle, image);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.on('GET', '/r/:id', async (req, res, params) => {
|
||||||
|
const image = await prisma.image.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ file: params.id },
|
||||||
|
{ invisible: { invis: decodeURI(params.id) } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!image) await rawFile(req, res, nextServer, params.id);
|
||||||
|
|
||||||
|
if (image.password) await handle(req, res);
|
||||||
|
else await rawFileDb(req, res, nextServer, prisma, image);
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextServer.prepare();
|
||||||
|
|
||||||
|
const http = createServer((req, res) => {
|
||||||
|
router.lookup(req, res);
|
||||||
|
if (config.core.logger) log(req.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
http.on('error', (e) => {
|
||||||
|
logger.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
http.on('listening', () => {
|
||||||
|
logger.info(`Listening on ${config.core.host}:${config.core.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
http.listen(config.core.port, config.core.host ?? '0.0.0.0');
|
||||||
|
|
||||||
|
stats(prisma);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rawFile(
|
||||||
|
req: IncomingMessage,
|
||||||
|
res: OutgoingMessage,
|
||||||
|
nextServer: NextServer,
|
||||||
|
id: string,
|
||||||
|
) {
|
||||||
|
const data = datasource.get(id);
|
||||||
|
if (!data) return 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', () => nextServer.render404(req, res as ServerResponse));
|
||||||
|
data.on('end', () => res.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rawFileDb(
|
||||||
|
req: IncomingMessage,
|
||||||
|
res: OutgoingMessage,
|
||||||
|
nextServer: NextServer,
|
||||||
|
prisma: PrismaClient,
|
||||||
|
image: Image,
|
||||||
|
) {
|
||||||
|
const data = datasource.get(image.file);
|
||||||
|
if (!data) return nextServer.render404(req, res as ServerResponse);
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', image.mimetype);
|
||||||
|
data.pipe(res);
|
||||||
|
data.on('error', () => nextServer.render404(req, res as ServerResponse));
|
||||||
|
data.on('end', () => res.end());
|
||||||
|
|
||||||
|
await prisma.image.update({
|
||||||
|
where: { id: image.id },
|
||||||
|
data: { views: { increment: 1 } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileDb(
|
||||||
|
req: IncomingMessage,
|
||||||
|
res: OutgoingMessage,
|
||||||
|
nextServer: NextServer,
|
||||||
|
prisma: PrismaClient,
|
||||||
|
handle: RequestHandler,
|
||||||
|
image: Image,
|
||||||
|
) {
|
||||||
|
const ext = image.file.split('.').pop();
|
||||||
|
if (Object.keys(exts).includes(ext)) return 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', () => nextServer.render404(req, res as ServerResponse));
|
||||||
|
data.on('end', () => res.end());
|
||||||
|
|
||||||
|
await prisma.image.update({
|
||||||
|
where: { id: image.id },
|
||||||
|
data: { views: { increment: 1 } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stats(prisma: PrismaClient) {
|
||||||
|
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) logger.info('stats updated');
|
||||||
|
}, config.core.stats_interval * 1000);
|
||||||
|
}
|
|
@ -1,183 +0,0 @@
|
||||||
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/datasource';
|
|
||||||
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';
|
|
||||||
|
|
||||||
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() {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async start() {
|
|
||||||
// annoy user if they didnt change secret from default "changethis"
|
|
||||||
if (config.core.secret === 'changethis') {
|
|
||||||
serverLog.error('Secret is not set!');
|
|
||||||
serverLog.error('Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!');
|
|
||||||
serverLog.error('Please change your secret in the config file or environment variables.');
|
|
||||||
serverLog.error('The config file is located at `config.toml`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.');
|
|
||||||
serverLog.error('It is recomended to use a secret that is alphanumeric and randomized. A way you can generate this is through a password manager you may have.');
|
|
||||||
process.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
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(^[^\\.]+\\.[^\\.]+)' : `${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) } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(image);
|
|
||||||
|
|
||||||
if (!image) await this.rawFile(req, res, params.id);
|
|
||||||
|
|
||||||
if (image.password) await this.handle(req, res);
|
|
||||||
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) } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!image) await this.rawFile(req, res, params.id);
|
|
||||||
|
|
||||||
if (image.password) await this.handle(req, res);
|
|
||||||
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: Image) {
|
|
||||||
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: Image) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue