Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd3ef5d13d | |||
| 322b72c511 | |||
| 508806bacd | |||
| c3af696cae | |||
| 14674bc7ac |
@@ -403,6 +403,22 @@ async function ensureSchema() {
|
||||
if (templateRequestSourceTierListColumns[0]?.Null !== 'YES') {
|
||||
await query('ALTER TABLE template_requests MODIFY source_tierlist_id VARCHAR(64) NULL')
|
||||
}
|
||||
const templateRequestTypeColumns = await query("SHOW COLUMNS FROM template_requests LIKE 'request_type'")
|
||||
if (!templateRequestTypeColumns.length) {
|
||||
await query("ALTER TABLE template_requests ADD COLUMN request_type VARCHAR(20) NOT NULL DEFAULT 'create' AFTER id")
|
||||
}
|
||||
const templateRequestSourceGameColumns = await query("SHOW COLUMNS FROM template_requests LIKE 'source_game_id'")
|
||||
if (!templateRequestSourceGameColumns.length) {
|
||||
await query("ALTER TABLE template_requests ADD COLUMN source_game_id VARCHAR(120) NOT NULL DEFAULT 'freeform' AFTER source_tierlist_id")
|
||||
}
|
||||
const templateRequestTargetGameColumns = await query("SHOW COLUMNS FROM template_requests LIKE 'target_game_id'")
|
||||
if (!templateRequestTargetGameColumns.length) {
|
||||
await query("ALTER TABLE template_requests ADD COLUMN target_game_id VARCHAR(120) NOT NULL DEFAULT '' AFTER source_game_id")
|
||||
}
|
||||
const templateRequestStatusColumns = await query("SHOW COLUMNS FROM template_requests LIKE 'status'")
|
||||
if (!templateRequestStatusColumns.length) {
|
||||
await query("ALTER TABLE template_requests ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending' AFTER target_game_id")
|
||||
}
|
||||
const templateRequestGroupsColumns = await query("SHOW COLUMNS FROM template_requests LIKE 'groups_json'")
|
||||
if (!templateRequestGroupsColumns.length) {
|
||||
await query("ALTER TABLE template_requests ADD COLUMN groups_json LONGTEXT NOT NULL AFTER items_json")
|
||||
@@ -427,6 +443,8 @@ async function ensureSchema() {
|
||||
const tierListSourceIdColumns = await query("SHOW COLUMNS FROM tierlists LIKE 'source_tierlist_id'")
|
||||
if (!tierListSourceIdColumns.length) {
|
||||
await query("ALTER TABLE tierlists ADD COLUMN source_tierlist_id VARCHAR(64) NULL DEFAULT NULL AFTER show_character_names")
|
||||
} else if (tierListSourceIdColumns[0]?.Null !== 'YES') {
|
||||
await query('ALTER TABLE tierlists MODIFY source_tierlist_id VARCHAR(64) NULL DEFAULT NULL')
|
||||
}
|
||||
const tierListSourceTitleColumns = await query("SHOW COLUMNS FROM tierlists LIKE 'source_snapshot_title'")
|
||||
if (!tierListSourceTitleColumns.length) {
|
||||
@@ -2003,6 +2021,7 @@ async function saveTierList({
|
||||
return findTierListById(existing.id, authorId)
|
||||
}
|
||||
|
||||
const nextId = id || nanoid()
|
||||
const createdAt = now()
|
||||
await query(
|
||||
`
|
||||
@@ -2011,9 +2030,9 @@ async function saveTierList({
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[id, authorId, gameId, title, nextThumbnailSrc, description || '', isPublic ? 1 : 0, showCharacterNames ? 1 : 0, sourceTierListId || null, sourceSnapshotTitle || '', sourceSnapshotAuthor || '', serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
[nextId, authorId, gameId, title, nextThumbnailSrc, description || '', isPublic ? 1 : 0, showCharacterNames ? 1 : 0, sourceTierListId || null, sourceSnapshotTitle || '', sourceSnapshotAuthor || '', serializeJson(groups), serializeJson(pool), createdAt, createdAt]
|
||||
)
|
||||
return findTierListById(id, authorId)
|
||||
return findTierListById(nextId, authorId)
|
||||
}
|
||||
|
||||
async function duplicateTierListForUser({ tierList, targetUserId }) {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-01 v1.3.45
|
||||
- 템플릿 요청에서 `내 티어리스트에도 저장`은 별도 부가 기능이 아니라 실제 저장본 생성 경로를 타므로, 새 저장본 ID는 호출자에 기대지 말고 저장 함수 내부에서 항상 보장하는 편이 더 안전하다고 정리했다.
|
||||
- 개발 단계의 내부 조치 문구인 `백엔드 재시작` 같은 표현은 사용자 토스트에 직접 노출하지 않고, 운영형 재시도 안내로 낮추는 편이 맞다고 판단했다.
|
||||
|
||||
## 2026-04-01 v1.3.44
|
||||
- 관리자 티어표 목록에서는 `보기` 버튼을 없애더라도 완성본 확인 기능 자체는 유지해야 하므로, 별도 액션 버튼보다 카드 썸네일 클릭을 미리보기 진입점으로 쓰는 편이 더 자연스럽다고 정리했다.
|
||||
- 템플릿 요청 미리보기도 별도 요약 카드보다 실제 보드 구조를 우선 보여주는 쪽이 관리자 검수 흐름에 더 맞으므로, 일반 티어표 미리보기와 가까운 방향으로 통일하기로 했다.
|
||||
|
||||
## 2026-03-30 v1.2.25
|
||||
- 홈 게임 카드는 메인 썸네일까지 없애는 것보다, 큰 썸네일은 유지하고 ID 옆의 작은 보조 표시만 제거하는 편이 원래 의도와 맞다고 정리했다.
|
||||
- 좌우 하단 액션 여백은 `margin`으로 푸터 전체를 밀기보다, 푸터 내부 `padding-bottom`으로 확보해야 버튼 자체는 항상 보이고 여백만 남는다고 판단했다.
|
||||
|
||||
@@ -20,3 +20,4 @@
|
||||
- 가이드 모달과 관리자 아이템 모달은 현재 같은 톤의 큰 셸을 쓰므로, 이후 공통 모달 레이아웃 컴포넌트로 분리할지 검토한다.
|
||||
|
||||
- 관리자 아이템 라이브러리 이름 변경은 템플릿·사용자 업로드·보관 자산까지 모두 가능하므로, 이후에는 일괄 이름 정리나 중복 이름 감지 보조 기능까지 검토한다.
|
||||
- 관리자 템플릿 요청 미리보기는 실제 완성본 모달과 더 가까운 체감이 되도록, 이후에도 보드 여백·행/열 헤더·남은 아이템 밀도를 한 번 더 비교 QA한다.
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-01 v1.3.45
|
||||
- 템플릿 요청에서 `내 티어리스트에도 저장`이 켜져 있을 때 발생하던 500 오류는 새 저장본 생성 시 `tierlists.id`에 `undefined`가 들어가던 문제였고, 이제 `saveTierList()`가 생성 시 자동으로 `nanoid()`를 부여하도록 고쳐 저장 분기 자체를 안정화함.
|
||||
- 사용자에게 노출되던 `백엔드를 재시작해주세요` 문구는 제거하고, 저장 분기 실패 시에도 일반적인 재시도 안내만 보이도록 조정함.
|
||||
- 루트에 잘못 남아 있던 `update.md` 진입점 파일은 제거하고, 업데이트 기록은 다시 `docs/update.md` 한 곳으로 정리함.
|
||||
|
||||
## 2026-04-01 v1.3.44
|
||||
- 관리자 `전체 티어표 관리`에서는 별도 `완성본 보기` 버튼은 다시 두지 않되, 카드 썸네일 자체를 눌러 기존처럼 완성본 미리보기 모달을 열 수 있게 복구함.
|
||||
- `템플릿 요청 관리`의 요청 미리보기는 요약 썸네일 중심 레이아웃을 줄이고, 실제 보드 구조를 먼저 읽는 방향으로 정리해 일반 티어표 완성본을 보는 흐름과 더 비슷하게 맞춤.
|
||||
|
||||
## 2026-04-01 v1.3.43
|
||||
- 템플릿 요청 모달은 `내 티어리스트에도 저장` 토글 상태를 요청 직전에 별도로 고정해 사용하도록 바꿔, 모달이 닫히며 draft가 초기화된 뒤 성공 토스트가 반대로 나오던 문제를 바로잡음.
|
||||
- 따라서 저장을 끈 상태에서는 `요청만 보냈어요` 문구가 정확히 유지되고, 저장을 켠 상태에서 500이 나는 경우에는 저장 단계에서 실패했다는 안내를 더 분명하게 보여주도록 보강함.
|
||||
|
||||
## 2026-04-01 v1.3.42
|
||||
- 템플릿 요청 시 `내 티어리스트에도 저장`이 켜져 있을 때만 500 오류가 날 수 있던 레거시 `tierlists.source_tierlist_id` nullability 문제도 함께 보강해, 오래된 DB 스키마에서도 요청 전 저장 흐름이 막히지 않도록 정리함.
|
||||
- 따라서 템플릿 요청 관련 레거시 호환 보정은 `template_requests`와 `tierlists` 양쪽에 모두 반영됐고, 실제 적용을 위해서는 백엔드 재시작 후 재확인이 필요함.
|
||||
|
||||
## 2026-04-01 v1.3.41
|
||||
- 템플릿 요청 등록 시 500 오류가 날 수 있던 레거시 DB 호환 문제를 보강해, 기존 `template_requests` 테이블에 `request_type`, `source_game_id`, `target_game_id`, `status` 컬럼이 빠져 있어도 서버 시작 시 자동으로 마이그레이션되도록 함.
|
||||
- 따라서 저장 여부와 무관하게 템플릿 요청 흐름은 유지되고, 구버전 DB를 사용 중이더라도 백엔드 재시작 후 같은 요청이 정상 저장되도록 안정성을 높임.
|
||||
|
||||
## 2026-04-01 v1.3.40
|
||||
- 관리자 아이템 상세 모달은 내부 스크롤바를 숨기고 본문 스크롤이 배경으로 전파되지 않도록 body scroll lock과 ESC 닫기를 추가해, 검수 중 배경 화면이 함께 움직이던 불편을 줄임.
|
||||
- 관리자 티어표 관리에서는 `완성본 보기` 흐름을 제거하고, 전체 티어표의 추가 아이템을 클릭하면 같은 아이템 관리 모달로 열어 게임 검색·템플릿 추가·새 템플릿 생성까지 같은 문법으로 처리할 수 있게 통일함.
|
||||
|
||||
@@ -1823,10 +1823,10 @@ async function saveFeaturedOrder() {
|
||||
<div v-if="!adminTierLists.length" class="hint">조건에 맞는 티어표가 없어요.</div>
|
||||
<div v-else class="tierAdminList">
|
||||
<article v-for="tierList in adminTierLists" :key="tierList.id" class="tierAdminCard">
|
||||
<div class="tierAdminCard__preview">
|
||||
<button class="tierAdminCard__preview" type="button" @click="openAdminTierList(tierList)">
|
||||
<img v-if="tierListThumbUrl(tierList)" class="tierAdminCard__thumb" :src="tierListThumbUrl(tierList)" :alt="tierList.title" />
|
||||
<div v-else class="tierAdminCard__thumb tierAdminCard__thumb--empty"></div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="tierAdminCard__body">
|
||||
<div class="tierAdminCard__head">
|
||||
@@ -2226,8 +2226,8 @@ async function saveFeaturedOrder() {
|
||||
</div>
|
||||
|
||||
<div v-if="previewTierList?.requestPreview" class="requestPreview">
|
||||
<div class="requestPreview__summary">
|
||||
<div class="requestPreview__summaryBody">
|
||||
<div class="requestPreview__hero">
|
||||
<div class="requestPreview__heroBody">
|
||||
<div v-if="previewTierList.description" class="requestPreview__desc">{{ previewTierList.description }}</div>
|
||||
<div class="requestPreview__meta">
|
||||
{{ previewRequestHasColumns(previewTierList) ? (previewRequestColumns(previewTierList).length + '열 구성') : '단일 열 구성' }} ·
|
||||
@@ -2235,14 +2235,8 @@ async function saveFeaturedOrder() {
|
||||
{{ previewTierList.snapshotItems?.length || 0 }}개 아이템
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
v-if="previewTierList.thumbnailSrc"
|
||||
class="requestPreview__summaryThumb"
|
||||
:src="toApiUrl(previewTierList.thumbnailSrc)"
|
||||
:alt="previewTierList.title"
|
||||
/>
|
||||
</div>
|
||||
<div class="requestPreview__board">
|
||||
<div class="requestPreview__board requestPreview__board--full">
|
||||
<div v-if="previewRequestHasColumns(previewTierList)" class="requestPreview__boardHead">
|
||||
<div class="requestPreview__rowLabel requestPreview__rowLabel--head">행</div>
|
||||
<div class="requestPreview__columnLabels" :style="previewRequestGridStyle(previewTierList)">
|
||||
@@ -4028,7 +4022,12 @@ async function saveFeaturedOrder() {
|
||||
padding: 16px;
|
||||
}
|
||||
.tierAdminCard__preview {
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
}
|
||||
.tierAdminCard__thumb {
|
||||
width: 100%;
|
||||
|
||||
@@ -762,6 +762,7 @@ async function toggleFavorite() {
|
||||
}
|
||||
|
||||
async function requestTemplate(type) {
|
||||
const shouldSaveToMyTierList = !!templateRequestSaveToMyTierList.value
|
||||
try {
|
||||
isRequestingTemplate.value = true
|
||||
await uploadPendingCustomItems()
|
||||
@@ -775,7 +776,7 @@ async function requestTemplate(type) {
|
||||
thumbnailSrc: uploadedThumbnailSrc || thumbnailSrc.value || '',
|
||||
isPublic: !!isPublic.value,
|
||||
showCharacterNames: !!showCharacterNames.value,
|
||||
saveToMyTierList: !!templateRequestSaveToMyTierList.value,
|
||||
saveToMyTierList: shouldSaveToMyTierList,
|
||||
groups: buildGroupPayload(),
|
||||
boardItems: Object.values(itemsById.value),
|
||||
})
|
||||
@@ -798,10 +799,10 @@ async function requestTemplate(type) {
|
||||
if (type === 'update') closeTemplateUpdateModal()
|
||||
toast.success(
|
||||
type === 'create'
|
||||
? templateRequestSaveToMyTierList.value
|
||||
? shouldSaveToMyTierList
|
||||
? '템플릿 등록 요청과 내 티어표 저장을 함께 완료했어요.'
|
||||
: '템플릿 등록 요청을 보냈어요.'
|
||||
: templateRequestSaveToMyTierList.value
|
||||
: shouldSaveToMyTierList
|
||||
? '템플릿 업데이트 요청과 내 티어표 저장을 함께 완료했어요.'
|
||||
: '템플릿 업데이트 요청을 보냈어요.'
|
||||
)
|
||||
@@ -822,6 +823,10 @@ async function requestTemplate(type) {
|
||||
toast.error('요청 제목, 설명, 아이템 이름 중 길이 제한을 넘긴 값이 없는지 확인해주세요.')
|
||||
return
|
||||
}
|
||||
if (e?.status === 500 && shouldSaveToMyTierList) {
|
||||
toast.error('템플릿 요청 중 내 티어리스트 저장에 실패했어요. 잠시 후 다시 시도해주세요.')
|
||||
return
|
||||
}
|
||||
toast.error(type === 'create' ? '템플릿 등록 요청에 실패했어요.' : '템플릿 업데이트 요청에 실패했어요.')
|
||||
} finally {
|
||||
isRequestingTemplate.value = false
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Update Log Entry Point
|
||||
|
||||
이 프로젝트의 상세 업데이트 로그는 [docs/update.md](/Users/bicute/Desktop/zenn.dev/tier-cursor/docs/update.md)에 계속 누적됩니다.
|
||||
|
||||
## 2026-03-30
|
||||
|
||||
- 루트 `package.json`에 공용 실행 스크립트(`dev:frontend`, `dev:backend`, `build`, `start`)를 추가했습니다.
|
||||
- 루트에서도 바로 `npm run build` 같은 공용 명령을 사용할 수 있게 정리했습니다.
|
||||
- 업데이트 로그 진입점을 루트 `update.md`로 추가해, 이후 작업 시 파일 위치를 바로 찾을 수 있게 했습니다.
|
||||
Reference in New Issue
Block a user