2021-06-23 13:20:20 -05:00
|
|
|
const next = require('next');
|
|
|
|
const { createServer } = require('http');
|
2022-01-03 18:56:33 -05:00
|
|
|
const { stat, mkdir, readdir } = require('fs/promises');
|
2021-06-23 13:20:20 -05:00
|
|
|
const { execSync } = require('child_process');
|
2022-01-03 18:56:33 -05:00
|
|
|
const { extname, join } = require('path');
|
2021-06-23 13:20:20 -05:00
|
|
|
const { PrismaClient } = require('@prisma/client');
|
|
|
|
const validateConfig = require('./validateConfig');
|
|
|
|
const Logger = require('../src/lib/logger');
|
|
|
|
const getFile = require('./static');
|
2021-08-25 11:58:48 -05:00
|
|
|
const prismaRun = require('../scripts/prisma-run');
|
2021-06-23 13:20:20 -05:00
|
|
|
const readConfig = require('../src/lib/readConfig');
|
2021-06-23 23:34:38 -05:00
|
|
|
const mimes = require('../scripts/mimes');
|
2021-08-25 11:58:48 -05:00
|
|
|
const deployDb = require('../scripts/deploy-db');
|
2021-09-03 19:03:47 -05:00
|
|
|
const { version } = require('../package.json');
|
2021-06-23 23:34:38 -05:00
|
|
|
|
2021-09-03 19:03:47 -05:00
|
|
|
Logger.get('server').info(`starting zipline@${version} server`);
|
2021-06-23 13:20:20 -05:00
|
|
|
|
|
|
|
const dev = process.env.NODE_ENV === 'development';
|
|
|
|
|
|
|
|
function log(url, status) {
|
|
|
|
if (url.startsWith('/_next') || url.startsWith('/__nextjs')) return;
|
2022-01-03 18:17:47 -05:00
|
|
|
return Logger.get('url').info(url);
|
2021-06-23 13:20:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function shouldUseYarn() {
|
|
|
|
try {
|
|
|
|
execSync('yarnpkg --version', { stdio: 'ignore' });
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(async () => {
|
|
|
|
try {
|
2021-09-24 22:31:45 -05:00
|
|
|
const a = readConfig();
|
|
|
|
const config = await validateConfig(a);
|
2021-06-23 13:20:20 -05:00
|
|
|
|
2021-08-28 13:32:09 -05:00
|
|
|
const data = await prismaRun(config.core.database_url, ['migrate', 'status'], true);
|
2021-11-27 17:39:57 -05:00
|
|
|
if (data.match(/Following migrations? have not yet been applied/)) {
|
2021-08-25 11:58:48 -05:00
|
|
|
Logger.get('database').info('some migrations are not applied, applying them now...');
|
|
|
|
await deployDb(config);
|
|
|
|
Logger.get('database').info('finished applying migrations');
|
2021-09-17 22:39:20 -05:00
|
|
|
} else Logger.get('database').info('migrations up to date');
|
2021-08-28 13:32:09 -05:00
|
|
|
process.env.DATABASE_URL = config.core.database_url;
|
2021-06-23 13:20:20 -05:00
|
|
|
|
|
|
|
await mkdir(config.uploader.directory, { recursive: true });
|
|
|
|
|
|
|
|
const app = next({
|
|
|
|
dir: '.',
|
|
|
|
dev,
|
2021-09-24 22:31:45 -05:00
|
|
|
quiet: dev,
|
2021-06-23 13:20:20 -05:00
|
|
|
}, config.core.port, config.core.host);
|
|
|
|
|
|
|
|
await app.prepare();
|
2021-09-03 19:03:47 -05:00
|
|
|
await stat('./.next');
|
2021-06-23 13:20:20 -05:00
|
|
|
|
|
|
|
const handle = app.getRequestHandler();
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
|
|
|
|
const srv = createServer(async (req, res) => {
|
2021-09-03 18:26:04 -05:00
|
|
|
if (req.url.startsWith('/r')) {
|
2021-06-23 13:20:20 -05:00
|
|
|
const parts = req.url.split('/');
|
|
|
|
if (!parts[2] || parts[2] === '') return;
|
|
|
|
|
2021-08-30 22:56:34 -05:00
|
|
|
let image = await prisma.image.findFirst({
|
|
|
|
where: {
|
2021-09-02 22:59:30 -05:00
|
|
|
OR: [
|
|
|
|
{ file: parts[2] },
|
2021-09-24 22:31:45 -05:00
|
|
|
{ invisible:{ invis: decodeURI(parts[2]) } },
|
|
|
|
],
|
2021-08-30 22:56:34 -05:00
|
|
|
},
|
|
|
|
select: {
|
|
|
|
mimetype: true,
|
|
|
|
id: true,
|
|
|
|
file: true,
|
2021-09-24 22:31:45 -05:00
|
|
|
invisible: true,
|
|
|
|
},
|
2021-08-30 22:56:34 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!image) {
|
|
|
|
const data = await getFile(config.uploader.directory, 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);
|
2021-06-23 13:20:20 -05:00
|
|
|
} else {
|
2022-01-03 22:00:20 -05:00
|
|
|
const data = await getFile(config.uploader.directory, image.file);
|
|
|
|
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 getFile(config.uploader.directory, parts[2]);
|
|
|
|
if (!data) return app.render404(req, res);
|
2021-08-30 22:56:34 -05:00
|
|
|
|
2022-01-03 22:00:20 -05:00
|
|
|
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 data = await getFile(config.uploader.directory, image.file);
|
|
|
|
if (!data) return app.render404(req, res);
|
2021-06-23 13:20:20 -05:00
|
|
|
|
2022-01-03 22:00:20 -05:00
|
|
|
await prisma.image.update({
|
|
|
|
where: { id: image.id },
|
|
|
|
data: { views: { increment: 1 } },
|
|
|
|
});
|
|
|
|
res.setHeader('Content-Type', image.mimetype);
|
|
|
|
res.end(data);
|
2021-06-23 13:20:20 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
handle(req, res);
|
|
|
|
}
|
|
|
|
|
2022-01-03 18:17:47 -05:00
|
|
|
if (config.core.logger) log(req.url, res.statusCode);
|
2021-06-23 13:20:20 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
srv.on('error', (e) => {
|
|
|
|
Logger.get('server').error(e);
|
|
|
|
|
|
|
|
process.exit(1);
|
|
|
|
});
|
|
|
|
srv.on('listening', () => {
|
|
|
|
Logger.get('server').info(`listening on ${config.core.host}:${config.core.port}`);
|
2021-09-12 23:17:27 -05:00
|
|
|
if (process.platform === 'linux' && dev) execSync(`xdg-open ${config.core.secure ? 'https' : 'http'}://${config.core.host === '0.0.0.0' ? 'localhost' : config.core.host}:${config.core.port}`);
|
2021-06-23 13:20:20 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
srv.listen(config.core.port, config.core.host ?? '0.0.0.0');
|
2022-01-03 18:56:33 -05:00
|
|
|
|
|
|
|
const stats = await getStats(prisma, config);
|
|
|
|
await prisma.stats.create({
|
|
|
|
data: {
|
|
|
|
data: stats,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
setInterval(async () => {
|
|
|
|
const stats = await getStats(prisma, config);
|
|
|
|
await prisma.stats.create({
|
|
|
|
data: {
|
|
|
|
data: stats,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (config.core.logger) Logger.get('server').info('stats updated');
|
|
|
|
}, config.core.stats_interval * 1000);
|
2021-06-23 13:20:20 -05:00
|
|
|
} catch (e) {
|
|
|
|
if (e.message && e.message.startsWith('Could not find a production')) {
|
|
|
|
Logger.get('web').error(`there is no production build - run \`${shouldUseYarn() ? 'yarn build' : 'npm build'}\``);
|
|
|
|
} else if (e.code && e.code === 'ENOENT') {
|
|
|
|
if (e.path === './.next') Logger.get('web').error(`there is no production build - run \`${shouldUseYarn() ? 'yarn build' : 'npm build'}\``);
|
|
|
|
} else {
|
|
|
|
Logger.get('server').error(e);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
}
|
2021-11-27 17:39:57 -05:00
|
|
|
})();
|
2022-01-03 18:56:33 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
function bytesToRead(bytes) {
|
|
|
|
const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
|
|
|
|
let num = 0;
|
|
|
|
|
|
|
|
while (bytes > 1024) {
|
|
|
|
bytes /= 1024;
|
|
|
|
++num;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${bytes.toFixed(1)} ${units[num]}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function getStats(prisma, config) {
|
|
|
|
const size = await sizeOfDir(join(process.cwd(), config.uploader.directory));
|
|
|
|
const byUser = await prisma.image.groupBy({
|
|
|
|
by: ['userId'],
|
|
|
|
_count: {
|
|
|
|
_all: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const count_users = await prisma.user.count();
|
|
|
|
|
|
|
|
const count_by_user = [];
|
|
|
|
for (let i = 0, L = byUser.length; i !== L; ++i) {
|
|
|
|
const user = await prisma.user.findFirst({
|
|
|
|
where: {
|
|
|
|
id: byUser[i].userId,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
count_by_user.push({
|
|
|
|
username: user.username,
|
|
|
|
count: byUser[i]._count._all,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const count = await prisma.image.count();
|
|
|
|
const viewsCount = await prisma.image.groupBy({
|
|
|
|
by: ['views'],
|
|
|
|
_sum: {
|
|
|
|
views: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const typesCount = await prisma.image.groupBy({
|
|
|
|
by: ['mimetype'],
|
|
|
|
_count: {
|
|
|
|
mimetype: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const types_count = [];
|
|
|
|
for (let i = 0, L = typesCount.length; i !== L; ++i) types_count.push({ mimetype: typesCount[i].mimetype, count: typesCount[i]._count.mimetype });
|
|
|
|
|
|
|
|
return {
|
|
|
|
size: bytesToRead(size),
|
|
|
|
size_num: size,
|
|
|
|
count,
|
|
|
|
count_by_user: count_by_user.sort((a,b) => b.count-a.count),
|
|
|
|
count_users,
|
|
|
|
views_count: (viewsCount[0]?._sum?.views ?? 0),
|
|
|
|
types_count: types_count.sort((a,b) => b.count-a.count),
|
|
|
|
};
|
|
|
|
}
|