mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-04-20 20:59:43 +08:00
371 lines
15 KiB
JavaScript
371 lines
15 KiB
JavaScript
import express from '@/vendor/express';
|
|
import $ from '@/core/app';
|
|
import migrate from '@/utils/migration';
|
|
import download from '@/utils/download';
|
|
import { syncArtifacts, produceArtifact } from '@/restful/sync';
|
|
import { gistBackupAction } from '@/restful/miscs';
|
|
import { TOKENS_KEY } from '@/constants';
|
|
|
|
import registerSubscriptionRoutes from './subscriptions';
|
|
import registerCollectionRoutes from './collections';
|
|
import registerArtifactRoutes from './artifacts';
|
|
import registerFileRoutes from './file';
|
|
import registerTokenRoutes from './token';
|
|
import registerModuleRoutes from './module';
|
|
import registerSyncRoutes from './sync';
|
|
import registerDownloadRoutes from './download';
|
|
import registerSettingRoutes from './settings';
|
|
import registerPreviewRoutes from './preview';
|
|
import registerSortingRoutes from './sort';
|
|
import registerMiscRoutes from './miscs';
|
|
import registerNodeInfoRoutes from './node-info';
|
|
import registerParserRoutes from './parser';
|
|
|
|
export default function serve() {
|
|
let port;
|
|
let host;
|
|
if ($.env.isNode) {
|
|
port = eval('process.env.SUB_STORE_BACKEND_API_PORT') || 3000;
|
|
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
|
|
}
|
|
const $app = express({ substore: $, port, host });
|
|
if ($.env.isNode) {
|
|
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
|
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
|
|
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
|
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
|
|
if (be_prefix || be_merge) {
|
|
if(!fe_be_path.startsWith('/')){
|
|
throw new Error(
|
|
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
|
|
);
|
|
}
|
|
$.info(
|
|
`[BACKEND PREFIX] ${host}:${port}${fe_be_path} -> ${host}:${port}`,
|
|
);
|
|
$app.use((req, res, next) => {
|
|
if (req.path.startsWith(fe_be_path)) {
|
|
req.url = req.url.replace(fe_be_path, '') || '/';
|
|
if(be_merge && req.url.startsWith('/api/')){
|
|
req.query['share'] = 'true';
|
|
}
|
|
next();
|
|
return;
|
|
}
|
|
const pathname = req._parsedUrl.pathname || '/';
|
|
if(be_merge && req.path.startsWith('/share/') && req.query.token){
|
|
if (req.method.toLowerCase() !== 'get'){
|
|
res.status(405).send('Method not allowed');
|
|
return;
|
|
}
|
|
const tokens = $.read(TOKENS_KEY) || [];
|
|
const token = tokens.find(
|
|
(t) =>
|
|
t.token === req.query.token &&
|
|
`/share/${t.type}/${t.name}` === pathname &&
|
|
(t.exp == null || t.exp > Date.now()),
|
|
);
|
|
if (token){
|
|
next();
|
|
return;
|
|
}
|
|
}
|
|
if (be_merge && fe_path && req.path.indexOf('/',1) == -1) {
|
|
if (req.path.indexOf('.') == -1){
|
|
req.url = "/index.html"
|
|
}
|
|
const express_ = eval(`require("express")`);
|
|
const mime_ = eval(`require("mime-types")`);
|
|
const path_ = eval(`require("path")`);
|
|
const staticFileMiddleware = express_.static(fe_path, {
|
|
setHeaders: (res, path) => {
|
|
const type = mime_.contentType(path_.extname(path));
|
|
if (type) {
|
|
res.set('Content-Type', type);
|
|
}
|
|
}
|
|
});
|
|
staticFileMiddleware(req, res, next);
|
|
return;
|
|
}
|
|
res.status(403).end('Forbbiden');
|
|
return;
|
|
});
|
|
}
|
|
}
|
|
// register routes
|
|
registerCollectionRoutes($app);
|
|
registerSubscriptionRoutes($app);
|
|
registerDownloadRoutes($app);
|
|
registerPreviewRoutes($app);
|
|
registerSortingRoutes($app);
|
|
registerSettingRoutes($app);
|
|
registerArtifactRoutes($app);
|
|
registerFileRoutes($app);
|
|
registerTokenRoutes($app);
|
|
registerModuleRoutes($app);
|
|
registerSyncRoutes($app);
|
|
registerNodeInfoRoutes($app);
|
|
registerMiscRoutes($app);
|
|
registerParserRoutes($app);
|
|
|
|
$app.start();
|
|
|
|
if ($.env.isNode) {
|
|
// Deprecated: SUB_STORE_BACKEND_CRON
|
|
const backend_sync_cron =
|
|
eval('process.env.SUB_STORE_BACKEND_SYNC_CRON') ||
|
|
eval('process.env.SUB_STORE_BACKEND_CRON');
|
|
if (backend_sync_cron) {
|
|
$.info(`[SYNC CRON] ${backend_sync_cron} enabled`);
|
|
const { CronJob } = eval(`require("cron")`);
|
|
new CronJob(
|
|
backend_sync_cron,
|
|
async function () {
|
|
try {
|
|
$.info(`[SYNC CRON] ${backend_sync_cron} started`);
|
|
await syncArtifacts();
|
|
$.info(`[SYNC CRON] ${backend_sync_cron} finished`);
|
|
} catch (e) {
|
|
$.error(
|
|
`[SYNC CRON] ${backend_sync_cron} error: ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
}
|
|
}, // onTick
|
|
null, // onComplete
|
|
true, // start
|
|
// 'Asia/Shanghai' // timeZone
|
|
);
|
|
}
|
|
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
|
|
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
|
|
const produce_cron = eval('process.env.SUB_STORE_PRODUCE_CRON');
|
|
if (produce_cron) {
|
|
$.info(`[PRODUCE CRON] ${produce_cron} enabled`);
|
|
const { CronJob } = eval(`require("cron")`);
|
|
produce_cron.split(/\s*;\s*/).map((item) => {
|
|
const [cron, type, name] = item.split(/\s*,\s*/);
|
|
new CronJob(
|
|
cron.trim(),
|
|
async function () {
|
|
try {
|
|
$.info(
|
|
`[PRODUCE CRON] ${type} ${name} ${cron} started`,
|
|
);
|
|
await produceArtifact({ type, name });
|
|
$.info(
|
|
`[PRODUCE CRON] ${type} ${name} ${cron} finished`,
|
|
);
|
|
} catch (e) {
|
|
$.error(
|
|
`[PRODUCE CRON] ${type} ${name} ${cron} error: ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
}
|
|
}, // onTick
|
|
null, // onComplete
|
|
true, // start
|
|
// 'Asia/Shanghai' // timeZone
|
|
);
|
|
});
|
|
}
|
|
const backend_download_cron = eval(
|
|
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
|
|
);
|
|
if (backend_download_cron) {
|
|
$.info(`[DOWNLOAD CRON] ${backend_download_cron} enabled`);
|
|
const { CronJob } = eval(`require("cron")`);
|
|
new CronJob(
|
|
backend_download_cron,
|
|
async function () {
|
|
try {
|
|
$.info(
|
|
`[DOWNLOAD CRON] ${backend_download_cron} started`,
|
|
);
|
|
await gistBackupAction('download');
|
|
$.info(
|
|
`[DOWNLOAD CRON] ${backend_download_cron} finished`,
|
|
);
|
|
} catch (e) {
|
|
$.error(
|
|
`[DOWNLOAD CRON] ${backend_download_cron} error: ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
}
|
|
}, // onTick
|
|
null, // onComplete
|
|
true, // start
|
|
// 'Asia/Shanghai' // timeZone
|
|
);
|
|
}
|
|
const backend_upload_cron = eval(
|
|
'process.env.SUB_STORE_BACKEND_UPLOAD_CRON',
|
|
);
|
|
if (backend_upload_cron) {
|
|
$.info(`[UPLOAD CRON] ${backend_upload_cron} enabled`);
|
|
const { CronJob } = eval(`require("cron")`);
|
|
new CronJob(
|
|
backend_upload_cron,
|
|
async function () {
|
|
try {
|
|
$.info(`[UPLOAD CRON] ${backend_upload_cron} started`);
|
|
await gistBackupAction('upload');
|
|
$.info(`[UPLOAD CRON] ${backend_upload_cron} finished`);
|
|
} catch (e) {
|
|
$.error(
|
|
`[UPLOAD CRON] ${backend_upload_cron} error: ${
|
|
e.message ?? e
|
|
}`,
|
|
);
|
|
}
|
|
}, // onTick
|
|
null, // onComplete
|
|
true, // start
|
|
// 'Asia/Shanghai' // timeZone
|
|
);
|
|
}
|
|
const path = eval(`require("path")`);
|
|
const fs = eval(`require("fs")`);
|
|
const data_url = eval('process.env.SUB_STORE_DATA_URL');
|
|
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
|
const fe_port = eval('process.env.SUB_STORE_FRONTEND_PORT') || 3001;
|
|
const fe_host =
|
|
eval('process.env.SUB_STORE_FRONTEND_HOST') || host || '::';
|
|
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
|
|
const fe_abs_path = path.resolve(
|
|
fe_path || path.join(__dirname, 'frontend'),
|
|
);
|
|
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
|
|
if (fe_path && !be_merge) {
|
|
try {
|
|
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
|
} catch (e) {
|
|
$.error(
|
|
`[FRONTEND] index.html file not found in ${fe_abs_path}`,
|
|
);
|
|
}
|
|
|
|
const express_ = eval(`require("express")`);
|
|
const history = eval(`require("connect-history-api-fallback")`);
|
|
const { createProxyMiddleware } = eval(
|
|
`require("http-proxy-middleware")`,
|
|
);
|
|
|
|
const app = express_();
|
|
|
|
const staticFileMiddleware = express_.static(fe_path);
|
|
|
|
let be_api = '/api/';
|
|
let be_download = '/download/';
|
|
let be_share = '/share/';
|
|
let be_download_rewrite = '';
|
|
let be_api_rewrite = '';
|
|
let be_share_rewrite = `${be_share}:type/:name`;
|
|
let prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX')
|
|
? fe_be_path
|
|
: '';
|
|
if (fe_be_path) {
|
|
if (!fe_be_path.startsWith('/')) {
|
|
throw new Error(
|
|
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
|
|
);
|
|
}
|
|
be_api_rewrite = `${
|
|
fe_be_path === '/' ? '' : fe_be_path
|
|
}${be_api}`;
|
|
be_download_rewrite = `${
|
|
fe_be_path === '/' ? '' : fe_be_path
|
|
}${be_download}`;
|
|
|
|
app.use(
|
|
be_share_rewrite,
|
|
createProxyMiddleware({
|
|
target: `http://127.0.0.1:${port}${prefix}`,
|
|
changeOrigin: true,
|
|
pathRewrite: async (path, req) => {
|
|
if (req.method.toLowerCase() !== 'get')
|
|
throw new Error('Method not allowed');
|
|
const tokens = $.read(TOKENS_KEY) || [];
|
|
const token = tokens.find(
|
|
(t) =>
|
|
t.token === req.query.token &&
|
|
t.type === req.params.type &&
|
|
t.name === req.params.name &&
|
|
(t.exp == null || t.exp > Date.now()),
|
|
);
|
|
if (!token) throw new Error('Forbbiden');
|
|
return req.originalUrl;
|
|
},
|
|
}),
|
|
);
|
|
app.use(
|
|
be_api_rewrite,
|
|
createProxyMiddleware({
|
|
target: `http://127.0.0.1:${port}${prefix}${be_api}`,
|
|
pathRewrite: async (path) => {
|
|
return path.includes('?')
|
|
? `${path}&share=true`
|
|
: `${path}?share=true`;
|
|
},
|
|
}),
|
|
);
|
|
app.use(
|
|
be_download_rewrite,
|
|
createProxyMiddleware({
|
|
target: `http://127.0.0.1:${port}${prefix}${be_download}`,
|
|
changeOrigin: true,
|
|
}),
|
|
);
|
|
}
|
|
|
|
app.use(staticFileMiddleware);
|
|
app.use(
|
|
history({
|
|
disableDotRule: true,
|
|
verbose: false,
|
|
}),
|
|
);
|
|
app.use(staticFileMiddleware);
|
|
|
|
const listener = app.listen(fe_port, fe_host, () => {
|
|
const { address: fe_address, port: fe_port } =
|
|
listener.address();
|
|
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
|
|
if (fe_be_path) {
|
|
$.info(
|
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> ${host}:${port}${prefix}${be_api}`,
|
|
);
|
|
$.info(
|
|
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> ${host}:${port}${prefix}${be_download}`,
|
|
);
|
|
$.info(
|
|
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
if (data_url) {
|
|
$.info(`[BACKEND] downloading data from ${data_url}`);
|
|
download(data_url)
|
|
.then((content) => {
|
|
$.write(content, '#sub-store');
|
|
|
|
$.cache = JSON.parse(content);
|
|
$.persistCache();
|
|
|
|
migrate();
|
|
$.info(`[BACKEND] restored data from ${data_url}`);
|
|
})
|
|
.catch((e) => {
|
|
$.error(`[BACKEND] restore data failed`);
|
|
console.error(e);
|
|
throw e;
|
|
});
|
|
}
|
|
}
|
|
}
|