mirror of
https://github.com/diced/zipline.git
synced 2025-12-05 20:40:12 -08:00
200 lines
5.9 KiB
TypeScript
200 lines
5.9 KiB
TypeScript
import { version } from '../../package.json';
|
|
import config from '../lib/config';
|
|
import datasource from '../lib/datasource';
|
|
import Logger from '../lib/logger';
|
|
import { getStats } from './util';
|
|
|
|
import fastify, { FastifyInstance } from 'fastify';
|
|
import { createReadStream, existsSync } from 'fs';
|
|
import dbFileDecorator from './decorators/dbFile';
|
|
import notFound from './decorators/notFound';
|
|
import postFileDecorator from './decorators/postFile';
|
|
import postUrlDecorator from './decorators/postUrl';
|
|
import preFileDecorator from './decorators/preFile';
|
|
import rawFileDecorator from './decorators/rawFile';
|
|
import configPlugin from './plugins/config';
|
|
import datasourcePlugin from './plugins/datasource';
|
|
import loggerPlugin from './plugins/logger';
|
|
import nextPlugin from './plugins/next';
|
|
import prismaPlugin from './plugins/prisma';
|
|
import rawRoute from './routes/raw';
|
|
import uploadsRoute, { uploadsRouteOnResponse } from './routes/uploads';
|
|
import urlsRoute, { urlsRouteOnResponse } from './routes/urls';
|
|
|
|
const dev = process.env.NODE_ENV === 'development';
|
|
const logger = Logger.get('server');
|
|
|
|
const server = fastify();
|
|
|
|
if (dev) {
|
|
server.addHook('onRoute', (opts) => {
|
|
logger.child('route').debug(JSON.stringify(opts));
|
|
});
|
|
}
|
|
|
|
start();
|
|
|
|
async function start() {
|
|
logger.debug('Starting server');
|
|
|
|
// plugins
|
|
server
|
|
.register(loggerPlugin)
|
|
.register(configPlugin, config)
|
|
.register(datasourcePlugin, datasource)
|
|
.register(prismaPlugin)
|
|
.register(nextPlugin, {
|
|
dir: '.',
|
|
dev,
|
|
quiet: !dev,
|
|
hostname: config.core.host,
|
|
port: config.core.port,
|
|
});
|
|
|
|
// decorators
|
|
server
|
|
.register(notFound)
|
|
.register(postUrlDecorator)
|
|
.register(postFileDecorator)
|
|
.register(preFileDecorator)
|
|
.register(rawFileDecorator)
|
|
.register(dbFileDecorator);
|
|
|
|
server.addHook('onRequest', (req, reply, done) => {
|
|
if (config.features.headless) {
|
|
const url = req.url.toLowerCase();
|
|
if (!url.startsWith('/api') || url === '/api') return reply.notFound();
|
|
}
|
|
|
|
done();
|
|
});
|
|
|
|
server.addHook('onResponse', (req, reply, done) => {
|
|
if (config.core.logger || dev || process.env.DEBUG) {
|
|
if (req.url.startsWith('/_next')) return done();
|
|
|
|
server.logger.child('response').info(`${req.method} ${req.url} -> ${reply.statusCode}`);
|
|
server.logger.child('response').debug(
|
|
JSON.stringify({
|
|
method: req.method,
|
|
url: req.url,
|
|
headers: req.headers,
|
|
body: req.headers['content-type']?.startsWith('application/json') ? req.body : undefined,
|
|
})
|
|
);
|
|
}
|
|
|
|
done();
|
|
});
|
|
|
|
server.get('/favicon.ico', async (_, reply) => {
|
|
if (!existsSync('./public/favicon.ico')) return reply.notFound();
|
|
|
|
const favicon = createReadStream('./public/favicon.ico');
|
|
return reply.type('image/x-icon').send(favicon);
|
|
});
|
|
|
|
// makes sure to handle both in one route as you cant have two handlers with the same route
|
|
if (config.urls.route === '/' && config.uploader.route === '/') {
|
|
server.route({
|
|
method: 'GET',
|
|
url: '/:id',
|
|
handler: async (req, reply) => {
|
|
const { id } = req.params as { id: string };
|
|
if (id === '') return reply.notFound();
|
|
else if (id === 'dashboard' && !config.features.headless)
|
|
return server.nextServer.render(req.raw, reply.raw, '/dashboard');
|
|
|
|
const url = await server.prisma.url.findFirst({
|
|
where: {
|
|
OR: [{ id: id }, { vanity: id }, { invisible: { invis: decodeURI(id) } }],
|
|
},
|
|
});
|
|
|
|
if (url) return urlsRoute.bind(server)(req, reply);
|
|
else return uploadsRoute.bind(server)(req, reply);
|
|
},
|
|
onResponse: async (req, reply, done) => {
|
|
if (reply.statusCode === 200) {
|
|
const { id } = req.params as { id: string };
|
|
|
|
const url = await server.prisma.url.findFirst({
|
|
where: {
|
|
OR: [{ id: id }, { vanity: id }, { invisible: { invis: decodeURI(id) } }],
|
|
},
|
|
});
|
|
|
|
if (url) urlsRouteOnResponse.bind(server)(req, reply, done);
|
|
else uploadsRouteOnResponse.bind(server)(req, reply, done);
|
|
}
|
|
|
|
done();
|
|
},
|
|
});
|
|
} else {
|
|
server
|
|
.route({
|
|
method: 'GET',
|
|
url: config.urls.route === '/' ? '/:id' : `${config.urls.route}/:id`,
|
|
handler: urlsRoute.bind(server),
|
|
onResponse: urlsRouteOnResponse.bind(server),
|
|
})
|
|
.route({
|
|
method: 'GET',
|
|
url: config.uploader.route === '/' ? '/:id' : `${config.uploader.route}/:id`,
|
|
handler: uploadsRoute.bind(server),
|
|
onResponse: uploadsRouteOnResponse.bind(server),
|
|
});
|
|
}
|
|
|
|
server.get('/r/:id', rawRoute.bind(server));
|
|
server.get('/', (_, reply) => reply.redirect('/dashboard'));
|
|
|
|
await server.listen({
|
|
port: config.core.port,
|
|
host: config.core.host ?? '0.0.0.0',
|
|
});
|
|
|
|
server.logger
|
|
.info(`listening on ${config.core.host}:${config.core.port}`)
|
|
.info(
|
|
`started ${dev ? 'development' : 'production'} zipline@${version} server${
|
|
config.features.headless ? ' (headless)' : ''
|
|
}`
|
|
);
|
|
|
|
clearInvites.bind(server)();
|
|
stats.bind(server)();
|
|
|
|
setInterval(() => clearInvites.bind(server)(), config.core.invites_interval * 1000);
|
|
setInterval(() => stats.bind(server)(), config.core.stats_interval * 1000);
|
|
}
|
|
|
|
async function stats(this: FastifyInstance) {
|
|
const stats = await getStats(this.prisma, this.datasource);
|
|
await this.prisma.stats.create({
|
|
data: {
|
|
data: stats,
|
|
},
|
|
});
|
|
|
|
this.logger.child('stats').debug(`stats updated ${JSON.stringify(stats)}`);
|
|
}
|
|
|
|
async function clearInvites(this: FastifyInstance) {
|
|
const { count } = await this.prisma.invite.deleteMany({
|
|
where: {
|
|
OR: [
|
|
{
|
|
expires_at: { lt: new Date() },
|
|
},
|
|
{
|
|
used: true,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
logger.child('invites').debug(`deleted ${count} used invites`);
|
|
}
|