v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.
세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1340,52 +1340,61 @@ const uploadLogo = async (event) => {
|
||||
|
||||
/**
|
||||
* 사이트 설정 PUT 요청 본문을 구성한다.
|
||||
* @param {Array<string>|null} [fields] - 포함할 필드 목록
|
||||
* @returns {Object} API 본문
|
||||
*/
|
||||
const buildSiteSettingsPayload = () => ({
|
||||
title: form.title,
|
||||
description: form.description,
|
||||
siteUrl: form.siteUrl,
|
||||
logoText: form.logoText || '井',
|
||||
logoUrl: form.logoUrl,
|
||||
faviconUrl: form.faviconUrl,
|
||||
copyrightText: form.copyrightText,
|
||||
socialLinks: normalizeSocialLinks(form.socialLinks),
|
||||
showPostUpdatedAt: Boolean(form.showPostUpdatedAt),
|
||||
postTagLimit: normalizePostTagLimit(form.postTagLimit),
|
||||
homeLatestPostLimit: normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT),
|
||||
postListPageSize: normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE),
|
||||
tagPostListPageSize: normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE),
|
||||
featuredPostLimit: normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT),
|
||||
pageSectionItemLimit: normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT),
|
||||
uploadMaxFileCount: normalizeUploadMaxFileCount(form.uploadMaxFileCount),
|
||||
uploadMaxTotalSize: normalizeUploadMaxTotalSize(form.uploadMaxTotalSize),
|
||||
homeCoverImageUrl: form.homeCoverImageUrl || '',
|
||||
homeCoverDarkImageUrl: form.homeCoverDarkImageUrl || '',
|
||||
homeCoverTitle: form.homeCoverTitle || '',
|
||||
homeCoverText: form.homeCoverText || '',
|
||||
brandColor: normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR),
|
||||
announcementEnabled: Boolean(form.announcementEnabled),
|
||||
announcementText: form.announcementText || '',
|
||||
announcementUrl: form.announcementUrl || '',
|
||||
announcementBackgroundColor: normalizeAnnouncementBackgroundColor(form.announcementBackgroundColor || DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR),
|
||||
announcementAlignment: normalizeAnnouncementAlignment(form.announcementAlignment || DEFAULT_ANNOUNCEMENT_ALIGNMENT),
|
||||
signupBlockedUsernames: normalizeSignupBlockedUsernames(form.signupBlockedUsernames),
|
||||
adsTxt: form.adsTxt || '',
|
||||
customHeadCode: form.customHeadCode || '',
|
||||
customFooterCode: form.customFooterCode || '',
|
||||
adHomeFeedCode: form.adHomeFeedCode || '',
|
||||
adHomeInfeedCode: form.adHomeInfeedCode || '',
|
||||
adSidebarCode: form.adSidebarCode || '',
|
||||
adPostSidebarCode: form.adPostSidebarCode || '',
|
||||
adPostTopCode: form.adPostTopCode || '',
|
||||
adPostInArticleCode: form.adPostInArticleCode || '',
|
||||
adPostBottomCode: form.adPostBottomCode || ''
|
||||
})
|
||||
const buildSiteSettingsPayload = (fields = null) => {
|
||||
const payload = {
|
||||
title: form.title,
|
||||
description: form.description,
|
||||
siteUrl: form.siteUrl,
|
||||
logoText: form.logoText || '井',
|
||||
logoUrl: form.logoUrl,
|
||||
faviconUrl: form.faviconUrl,
|
||||
copyrightText: form.copyrightText,
|
||||
socialLinks: normalizeSocialLinks(form.socialLinks),
|
||||
showPostUpdatedAt: Boolean(form.showPostUpdatedAt),
|
||||
postTagLimit: normalizePostTagLimit(form.postTagLimit),
|
||||
homeLatestPostLimit: normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT),
|
||||
postListPageSize: normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE),
|
||||
tagPostListPageSize: normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE),
|
||||
featuredPostLimit: normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT),
|
||||
pageSectionItemLimit: normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT),
|
||||
uploadMaxFileCount: normalizeUploadMaxFileCount(form.uploadMaxFileCount),
|
||||
uploadMaxTotalSize: normalizeUploadMaxTotalSize(form.uploadMaxTotalSize),
|
||||
homeCoverImageUrl: form.homeCoverImageUrl || '',
|
||||
homeCoverDarkImageUrl: form.homeCoverDarkImageUrl || '',
|
||||
homeCoverTitle: form.homeCoverTitle || '',
|
||||
homeCoverText: form.homeCoverText || '',
|
||||
brandColor: normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR),
|
||||
announcementEnabled: Boolean(form.announcementEnabled),
|
||||
announcementText: form.announcementText || '',
|
||||
announcementUrl: form.announcementUrl || '',
|
||||
announcementBackgroundColor: normalizeAnnouncementBackgroundColor(form.announcementBackgroundColor || DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR),
|
||||
announcementAlignment: normalizeAnnouncementAlignment(form.announcementAlignment || DEFAULT_ANNOUNCEMENT_ALIGNMENT),
|
||||
signupBlockedUsernames: normalizeSignupBlockedUsernames(form.signupBlockedUsernames),
|
||||
adsTxt: form.adsTxt || '',
|
||||
customHeadCode: form.customHeadCode || '',
|
||||
customFooterCode: form.customFooterCode || '',
|
||||
adHomeFeedCode: form.adHomeFeedCode || '',
|
||||
adHomeInfeedCode: form.adHomeInfeedCode || '',
|
||||
adSidebarCode: form.adSidebarCode || '',
|
||||
adPostSidebarCode: form.adPostSidebarCode || '',
|
||||
adPostTopCode: form.adPostTopCode || '',
|
||||
adPostInArticleCode: form.adPostInArticleCode || '',
|
||||
adPostBottomCode: form.adPostBottomCode || ''
|
||||
}
|
||||
|
||||
if (!Array.isArray(fields)) {
|
||||
return payload
|
||||
}
|
||||
|
||||
return Object.fromEntries(fields.map((field) => [field, payload[field]]))
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 폼 값으로 사이트 설정을 서버에 저장한다.
|
||||
* @param {{ successToast?: string, savingFlag: import('vue').Ref<boolean> }} options - 저장 옵션
|
||||
* @param {{ successToast?: string, savingFlag: import('vue').Ref<boolean>, fields: Array<string> }} options - 저장 옵션
|
||||
* @returns {Promise<boolean>} 성공 여부
|
||||
*/
|
||||
const persistSiteSettings = async (options) => {
|
||||
@@ -1395,11 +1404,14 @@ const persistSiteSettings = async (options) => {
|
||||
showToast('info', '설정을 저장하는 중입니다.')
|
||||
|
||||
try {
|
||||
const payload = buildSiteSettingsPayload(options.fields)
|
||||
const updatedSettings = await $fetch('/admin/api/settings', {
|
||||
method: 'PUT',
|
||||
body: buildSiteSettingsPayload()
|
||||
method: 'PATCH',
|
||||
body: payload
|
||||
})
|
||||
options.fields.forEach((field) => {
|
||||
form[field] = updatedSettings[field]
|
||||
})
|
||||
Object.assign(form, updatedSettings)
|
||||
showToast('success', successToast)
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -1442,7 +1454,8 @@ const saveTitleDescSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '블로그 제목·설명이 저장되었습니다.',
|
||||
savingFlag: savingTitleDesc
|
||||
savingFlag: savingTitleDesc,
|
||||
fields: ['title', 'description']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1487,7 +1500,8 @@ const saveMiscSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '사이트 정보가 저장되었습니다.',
|
||||
savingFlag: savingMisc
|
||||
savingFlag: savingMisc,
|
||||
fields: ['siteUrl', 'logoText', 'logoUrl', 'faviconUrl', 'copyrightText']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1574,7 +1588,8 @@ const saveSocialSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: 'SNS 정보가 저장되었습니다.',
|
||||
savingFlag: savingSocial
|
||||
savingFlag: savingSocial,
|
||||
fields: ['socialLinks']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1636,7 +1651,18 @@ const savePostSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: 'POST 설정이 저장되었습니다.',
|
||||
savingFlag: savingPost
|
||||
savingFlag: savingPost,
|
||||
fields: [
|
||||
'showPostUpdatedAt',
|
||||
'postTagLimit',
|
||||
'homeLatestPostLimit',
|
||||
'postListPageSize',
|
||||
'tagPostListPageSize',
|
||||
'featuredPostLimit',
|
||||
'pageSectionItemLimit',
|
||||
'uploadMaxFileCount',
|
||||
'uploadMaxTotalSize'
|
||||
]
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1811,7 +1837,13 @@ const saveHomeCoverSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '메인 화면 설정이 저장되었습니다.',
|
||||
savingFlag: savingHomeCover
|
||||
savingFlag: savingHomeCover,
|
||||
fields: [
|
||||
'homeCoverImageUrl',
|
||||
'homeCoverDarkImageUrl',
|
||||
'homeCoverTitle',
|
||||
'homeCoverText'
|
||||
]
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1863,7 +1895,8 @@ const saveBrandSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '브랜드 컬러가 저장되었습니다.',
|
||||
savingFlag: savingBrand
|
||||
savingFlag: savingBrand,
|
||||
fields: ['brandColor']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1909,7 +1942,14 @@ const saveAnnouncementSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '어나운스 바 설정이 저장되었습니다.',
|
||||
savingFlag: savingAnnouncement
|
||||
savingFlag: savingAnnouncement,
|
||||
fields: [
|
||||
'announcementEnabled',
|
||||
'announcementText',
|
||||
'announcementUrl',
|
||||
'announcementBackgroundColor',
|
||||
'announcementAlignment'
|
||||
]
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1951,7 +1991,8 @@ const saveSpamSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '스팸 필터 설정이 저장되었습니다.',
|
||||
savingFlag: savingSpam
|
||||
savingFlag: savingSpam,
|
||||
fields: ['signupBlockedUsernames']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -1993,7 +2034,8 @@ const saveSiteCodeSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '사이트 코드 설정이 저장되었습니다.',
|
||||
savingFlag: savingSiteCode
|
||||
savingFlag: savingSiteCode,
|
||||
fields: ['adsTxt', 'customHeadCode', 'customFooterCode']
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
@@ -2045,7 +2087,16 @@ const saveAdsSection = async () => {
|
||||
|
||||
const ok = await persistSiteSettings({
|
||||
successToast: '광고 슬롯 설정이 저장되었습니다.',
|
||||
savingFlag: savingAds
|
||||
savingFlag: savingAds,
|
||||
fields: [
|
||||
'adHomeFeedCode',
|
||||
'adHomeInfeedCode',
|
||||
'adSidebarCode',
|
||||
'adPostSidebarCode',
|
||||
'adPostTopCode',
|
||||
'adPostInArticleCode',
|
||||
'adPostBottomCode'
|
||||
]
|
||||
})
|
||||
|
||||
if (ok) {
|
||||
|
||||
Reference in New Issue
Block a user