76 lines
3.3 KiB
JavaScript
76 lines
3.3 KiB
JavaScript
const express = require('express')
|
|
const { findTierListById } = require('../db')
|
|
|
|
const router = express.Router()
|
|
const APP_ORIGIN = (process.env.APP_ORIGIN || 'http://localhost:5173').replace(/\/+$/, '')
|
|
const DEFAULT_TITLE = 'Tier Maker | 템플릿으로 쉽게 만드는 티어표'
|
|
const DEFAULT_DESCRIPTION = '템플릿과 커스텀 이미지로 티어표를 만들고 저장하고 공유하세요.'
|
|
const DEFAULT_IMAGE_URL = `${APP_ORIGIN}/og-card.png`
|
|
|
|
function escapeHtml(value) {
|
|
return String(value || '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
}
|
|
|
|
function toAbsoluteUrl(pathname) {
|
|
const src = String(pathname || '').trim()
|
|
if (!src) return DEFAULT_IMAGE_URL
|
|
if (/^https?:\/\//i.test(src)) return src
|
|
return `${APP_ORIGIN}${src.startsWith('/') ? src : `/${src}`}`
|
|
}
|
|
|
|
function buildShareHtml({ title, description, imageUrl, shareUrl, appUrl }) {
|
|
const safeTitle = escapeHtml(title || DEFAULT_TITLE)
|
|
const safeDescription = escapeHtml(description || DEFAULT_DESCRIPTION)
|
|
const safeImageUrl = escapeHtml(imageUrl || DEFAULT_IMAGE_URL)
|
|
const safeShareUrl = escapeHtml(shareUrl || APP_ORIGIN)
|
|
const safeAppUrl = escapeHtml(appUrl || APP_ORIGIN)
|
|
|
|
return `<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>${safeTitle}</title>
|
|
<meta name="description" content="${safeDescription}" />
|
|
<link rel="canonical" href="${safeAppUrl}" />
|
|
<meta property="og:site_name" content="Tier Maker" />
|
|
<meta property="og:locale" content="ko_KR" />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="${safeShareUrl}" />
|
|
<meta property="og:title" content="${safeTitle}" />
|
|
<meta property="og:description" content="${safeDescription}" />
|
|
<meta property="og:image" content="${safeImageUrl}" />
|
|
<meta property="og:image:alt" content="${safeTitle}" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="${safeTitle}" />
|
|
<meta name="twitter:description" content="${safeDescription}" />
|
|
<meta name="twitter:image" content="${safeImageUrl}" />
|
|
<meta http-equiv="refresh" content="0; url=${safeAppUrl}" />
|
|
</head>
|
|
<body>
|
|
<script>window.location.replace(${JSON.stringify(appUrl || APP_ORIGIN)})</script>
|
|
</body>
|
|
</html>`
|
|
}
|
|
|
|
router.get('/editor/:topicId/:tierListId', async (req, res) => {
|
|
const { topicId, tierListId } = req.params
|
|
const appUrl = `${APP_ORIGIN}/editor/${encodeURIComponent(topicId)}/${encodeURIComponent(tierListId)}?preview=1`
|
|
const shareUrl = `${APP_ORIGIN}${req.originalUrl || `/share/editor/${encodeURIComponent(topicId)}/${encodeURIComponent(tierListId)}`}`
|
|
|
|
const tierList = await findTierListById(tierListId)
|
|
const isPublicMatch = tierList?.isPublic && (tierList.topicSlug === topicId || tierList.topicId === topicId)
|
|
const title = isPublicMatch ? tierList.title : DEFAULT_TITLE
|
|
const description = isPublicMatch && tierList.description ? tierList.description : DEFAULT_DESCRIPTION
|
|
const imageUrl = isPublicMatch ? toAbsoluteUrl(tierList.thumbnailSrc) : DEFAULT_IMAGE_URL
|
|
|
|
res.type('html').send(buildShareHtml({ title, description, imageUrl, shareUrl, appUrl }))
|
|
})
|
|
|
|
module.exports = router
|