v1.7.1: 멤버 권한·추천 사이트·네비게이션 UI 개선
관리자는 VIP·멤버만 수정 가능하도록 서버·UI 권한을 정리하고 소유자 권한 이전 API와 확인 모달을 추가했다. 추천 사이트 파비콘 캐시·URL 접두사·카드 편집 UI를 보강하고 확인 얼럿을 AdminConfirmModal로 통일했다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -153,6 +153,8 @@ const mapNavigationItemRow = (row) => ({
|
||||
url: row.url,
|
||||
descriptionText: row.description_text || '',
|
||||
thumbnailUrl: row.thumbnail_url || '',
|
||||
cachedFaviconUrl: row.cached_favicon_url || '',
|
||||
faviconCachedAt: row.favicon_cached_at ? row.favicon_cached_at.toISOString() : '',
|
||||
location: row.location,
|
||||
sortOrder: row.sort_order,
|
||||
isVisible: row.is_visible,
|
||||
@@ -1131,6 +1133,7 @@ export const getPublicNavigation = async () => {
|
||||
url: item.url,
|
||||
descriptionText: item.descriptionText,
|
||||
thumbnailUrl: item.thumbnailUrl,
|
||||
cachedFaviconUrl: item.cachedFaviconUrl,
|
||||
isVisible: item.isVisible
|
||||
}))
|
||||
}
|
||||
@@ -1148,6 +1151,20 @@ export const updateNavigationItems = async (items) => {
|
||||
throw new Error('DATABASE_REQUIRED')
|
||||
}
|
||||
|
||||
const existingRows = await sql`
|
||||
SELECT id, cached_favicon_url, favicon_cached_at
|
||||
FROM navigation_items
|
||||
`
|
||||
const faviconCacheById = new Map(
|
||||
existingRows.map((row) => [
|
||||
String(row.id),
|
||||
{
|
||||
cachedFaviconUrl: row.cached_favicon_url || '',
|
||||
faviconCachedAt: row.favicon_cached_at ?? null
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
await sql.begin(async (transaction) => {
|
||||
await transaction`
|
||||
DELETE FROM navigation_items
|
||||
@@ -1155,6 +1172,11 @@ export const updateNavigationItems = async (items) => {
|
||||
|
||||
const ordered = orderNavigationItemsForInsert(items)
|
||||
for (const item of ordered) {
|
||||
const cached = faviconCacheById.get(String(item.id)) || {
|
||||
cachedFaviconUrl: '',
|
||||
faviconCachedAt: null
|
||||
}
|
||||
|
||||
await transaction`
|
||||
INSERT INTO navigation_items (
|
||||
id,
|
||||
@@ -1162,6 +1184,8 @@ export const updateNavigationItems = async (items) => {
|
||||
url,
|
||||
description_text,
|
||||
thumbnail_url,
|
||||
cached_favicon_url,
|
||||
favicon_cached_at,
|
||||
location,
|
||||
sort_order,
|
||||
is_visible,
|
||||
@@ -1174,6 +1198,8 @@ export const updateNavigationItems = async (items) => {
|
||||
${item.url},
|
||||
${item.descriptionText || ''},
|
||||
${item.thumbnailUrl || ''},
|
||||
${cached.cachedFaviconUrl},
|
||||
${cached.faviconCachedAt},
|
||||
${item.location},
|
||||
${item.sortOrder},
|
||||
${item.isVisible},
|
||||
@@ -1187,6 +1213,54 @@ export const updateNavigationItems = async (items) => {
|
||||
return listNavigationItems()
|
||||
}
|
||||
|
||||
/**
|
||||
* 네비게이션 항목 단건 조회
|
||||
* @param {string} id - 항목 ID
|
||||
* @returns {Promise<Object | null>} 네비게이션 항목
|
||||
*/
|
||||
export const getNavigationItemById = async (id) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
SELECT *
|
||||
FROM navigation_items
|
||||
WHERE id = ${id}
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
return rows[0] ? mapNavigationItemRow(rows[0]) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 추천 사이트 파비콘 캐시를 갱신한다.
|
||||
* @param {string} id - 항목 ID
|
||||
* @param {string} cachedFaviconUrl - 저장된 파비콘 URL
|
||||
* @returns {Promise<Object | null>} 갱신된 항목
|
||||
*/
|
||||
export const updateNavigationItemFaviconCache = async (id, cachedFaviconUrl) => {
|
||||
const sql = getPostgresClient()
|
||||
|
||||
if (!sql) {
|
||||
throw new Error('DATABASE_REQUIRED')
|
||||
}
|
||||
|
||||
const rows = await sql`
|
||||
UPDATE navigation_items
|
||||
SET
|
||||
cached_favicon_url = ${cachedFaviconUrl},
|
||||
favicon_cached_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = ${id}
|
||||
RETURNING *
|
||||
`
|
||||
|
||||
return rows[0] ? mapNavigationItemRow(rows[0]) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 태그 상세 조회
|
||||
* @param {string} id - 태그 ID
|
||||
|
||||
Reference in New Issue
Block a user