feat(ai-cases): 성공 사례 카드에 PDF 첫 슬라이드 썸네일·그라데이션 개선

- 목록에서 슬라이드 워밍(최대 24건) 후 coverImageUrl 전달
- 카드 상단 풀너비 이미지+스크림, 무이미지 시 다층 그라데이션

Made-with: Cursor
This commit is contained in:
2026-04-08 20:15:44 +09:00
parent 81244d34c9
commit 14fa47922a
3 changed files with 150 additions and 47 deletions

View File

@@ -1189,21 +1189,43 @@ pageRouter.get("/ai-cases/write", (req, res) => {
});
});
pageRouter.get("/ai-cases", (req, res) => {
const q = (req.query.q || "").trim();
const tag = (req.query.tag || "").trim();
const meta = loadAiSuccessStoriesMeta();
const filtered = filterAiSuccessStories(meta, q, tag);
const tags = allAiSuccessStoryTags(meta);
res.render("ai-cases", {
activeMenu: "ai-cases",
adminMode: res.locals.adminMode,
opsUserEmail: !!res.locals.opsUserEmail,
successStoryDetailAllowed: isAiSuccessStoryDetailAllowed(req, res),
stories: filtered,
filters: { q, tag },
availableTags: tags,
});
pageRouter.get("/ai-cases", async (req, res, next) => {
try {
const q = (req.query.q || "").trim();
const tag = (req.query.tag || "").trim();
const meta = loadAiSuccessStoriesMeta();
const filtered = filterAiSuccessStories(meta, q, tag);
const tags = allAiSuccessStoryTags(meta);
/** 목록에서도 카드 썸네일을 쓰기 위해 PDF→슬라이드가 없으면 생성 시도(상세와 동일 소스) */
await Promise.all(
filtered.slice(0, 24).map(async (m) => {
const pdfUrl = (m.pdfUrl || "").trim();
if (!pdfUrl) return;
if (getAiSuccessSlideImageUrls(m.slug).length > 0) return;
try {
await ensureAiSuccessStorySlides(m.slug, pdfUrl);
} catch (err) {
console.warn("[ai-cases] 슬라이드 워밍 실패:", m.slug, err?.message || err);
}
})
);
const stories = filtered.map((m) => {
const slideUrls = getAiSuccessSlideImageUrls(m.slug);
const coverImageUrl = slideUrls.length > 0 ? slideUrls[0] : "";
return { ...m, coverImageUrl };
});
res.render("ai-cases", {
activeMenu: "ai-cases",
adminMode: res.locals.adminMode,
opsUserEmail: !!res.locals.opsUserEmail,
successStoryDetailAllowed: isAiSuccessStoryDetailAllowed(req, res),
stories,
filters: { q, tag },
availableTags: tags,
});
} catch (err) {
next(err);
}
});
pageRouter.get("/ai-cases/:slug", async (req, res, next) => {