@@ -26,17 +26,12 @@ export async function regenerateTagImage(tag, mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const items = await db`
|
const items = await db`
|
||||||
SELECT id FROM (
|
SELECT i.id
|
||||||
SELECT i.id
|
FROM items i
|
||||||
FROM items i
|
JOIN tags_assign ta ON ta.item_id = i.id
|
||||||
JOIN tags_assign ta ON ta.item_id = i.id
|
JOIN tags t ON t.id = ta.tag_id
|
||||||
JOIN tags t ON t.id = ta.tag_id
|
${modeFilter}
|
||||||
${modeFilter}
|
WHERE (t.tag = ${tag} OR t.normalized = ${tag}) AND i.active = true
|
||||||
WHERE (t.tag = ${tag} OR t.normalized = ${tag})
|
|
||||||
AND i.active = true
|
|
||||||
AND i.is_deleted = false
|
|
||||||
LIMIT 100
|
|
||||||
) pool
|
|
||||||
ORDER BY RANDOM()
|
ORDER BY RANDOM()
|
||||||
LIMIT 3
|
LIMIT 3
|
||||||
`;
|
`;
|
||||||
@@ -144,20 +139,16 @@ function generateFallbackSvg(tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default (router, tpl) => {
|
export default (router, tpl) => {
|
||||||
router.get(/^\/tag_image\/(?<tag>[^?]+)$/, async (req, res) => {
|
router.get(/^\/tag_image\/(?<tag>.+)$/, async (req, res) => {
|
||||||
const tag = decodeURIComponent(req.params.tag);
|
const tag = decodeURIComponent(req.params.tag);
|
||||||
|
|
||||||
// Parse query parameters robustly
|
// Parse query parameters
|
||||||
let query = {};
|
let query = {};
|
||||||
try {
|
if (typeof req.url === 'string') {
|
||||||
if (typeof req.url === 'string') {
|
const parsedUrl = url.parse(req.url, true);
|
||||||
const parsedUrl = url.parse(req.url, true);
|
query = parsedUrl.query;
|
||||||
query = parsedUrl.query;
|
} else {
|
||||||
} else if (req.url && req.url.qs) {
|
query = req.url.qs || {};
|
||||||
query = req.url.qs;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[TAG_IMAGE] Query parse failed:', e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mode = query.m ? parseInt(query.m) : (req.mode ?? 0);
|
const mode = query.m ? parseInt(query.m) : (req.mode ?? 0);
|
||||||
@@ -172,8 +163,7 @@ export default (router, tpl) => {
|
|||||||
'Content-Type': 'image/webp',
|
'Content-Type': 'image/webp',
|
||||||
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`
|
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`
|
||||||
});
|
});
|
||||||
const content = await fs.readFile(cachePath);
|
return res.end(await fs.readFile(cachePath));
|
||||||
return res.end(content);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Cache miss, proceed to generation
|
// Cache miss, proceed to generation
|
||||||
@@ -182,16 +172,11 @@ export default (router, tpl) => {
|
|||||||
// Generate on-demand
|
// Generate on-demand
|
||||||
const generated = await regenerateTagImage(tag, mode);
|
const generated = await regenerateTagImage(tag, mode);
|
||||||
if (generated) {
|
if (generated) {
|
||||||
try {
|
res.writeHead(200, {
|
||||||
const content = await fs.readFile(generated);
|
'Content-Type': 'image/webp',
|
||||||
res.writeHead(200, {
|
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`
|
||||||
'Content-Type': 'image/webp',
|
});
|
||||||
'Cache-Control': `public, max-age=${CACHE_MAX_AGE}`
|
return res.end(await fs.readFile(generated));
|
||||||
});
|
|
||||||
return res.end(content);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[TAG_IMAGE] Failed to read generated file ${generated}:`, err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to deterministic SVG
|
// Fallback to deterministic SVG
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ process.on('uncaughtException', err => {
|
|||||||
return;
|
return;
|
||||||
if (req.url.pathname === '/manifest.json' || req.url.pathname === '/sw.js')
|
if (req.url.pathname === '/manifest.json' || req.url.pathname === '/sw.js')
|
||||||
return;
|
return;
|
||||||
if (req.url.pathname.match(/^\/(b|t|ca|a|memes|tag_image)\//) || req.url.pathname.startsWith('/s/emojis/')) {
|
if (req.url.pathname.match(/^\/(b|t|ca|a|memes)\//) || req.url.pathname.startsWith('/s/emojis/')) {
|
||||||
if (cfg.websrv.private_society && !req.cookies?.session) {
|
if (cfg.websrv.private_society && !req.cookies?.session) {
|
||||||
res.writeHead(502, { 'Content-Type': 'text/html' }).end(nginx502);
|
res.writeHead(502, { 'Content-Type': 'text/html' }).end(nginx502);
|
||||||
req.url.pathname = '/private_society_media_bypass';
|
req.url.pathname = '/private_society_media_bypass';
|
||||||
@@ -409,7 +409,7 @@ process.on('uncaughtException', err => {
|
|||||||
|
|
||||||
// Private Society gate — require login for all content when enabled
|
// Private Society gate — require login for all content when enabled
|
||||||
if (cfg.websrv.private_society && !req.session) {
|
if (cfg.websrv.private_society && !req.session) {
|
||||||
const publicPaths = /^\/(s|tag_image|login|logout|register|activate|forgot-password|reset-password|banned|api\/v2\/auth|manifest\.json|sw\.js|robots\.txt|favicon\.(ico|png|gif)|s\/img\/duck-icon-(192|512)\.png)(\/.*)?$/;
|
const publicPaths = /^\/(s|login|logout|register|activate|forgot-password|reset-password|banned|api\/v2\/auth|manifest\.json|sw\.js|robots\.txt|favicon\.(ico|png|gif)|s\/img\/duck-icon-(192|512)\.png)(\/.*)?$/;
|
||||||
if (!publicPaths.test(req.url.pathname)) {
|
if (!publicPaths.test(req.url.pathname)) {
|
||||||
// For AJAX requests, return 502 so it looks like the backend is down
|
// For AJAX requests, return 502 so it looks like the backend is down
|
||||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
|
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
|
||||||
|
|||||||
Reference in New Issue
Block a user