Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion docs/project/work.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- **슬로프캠 운영 환경 스트림 미출력 이슈 전수 조치 (2026-10-01)**:
- 상태: 구현, 빌드 검증, 마이그레이션 006 작성 완료
- 상태: main 머지(ec22a61), 백엔드·프론트엔드 운영 배포 완료, 운영 실측 검증 완료 (200 OK)
- 내용:
- **비발디파크 404 해결**: `frontend/Dockerfile`의 runner 스테이지에 `COPY --from=builder /app/public ./public` 추가하여 운영 컨테이너에 `vivaldi.html` 및 `vivaldi.js` 정적 에셋이 포함되도록 수정.
- **하이원·오투·무주 401 해결**: Nginx의 `/api/` ➔ 백엔드(8080) 라우팅 규칙과의 충돌을 피하기 위해 Next.js 스트림 프록시 라우트를 `/api/*-stream`에서 `/stream-proxy/*`(`high1`, `o2`, `muju`)로 분리 이동.
Expand All @@ -14,6 +14,17 @@
- **빌드 검증**:
- 프론트엔드 Turbopack 프로덕션 빌드(`npm run build`) 통과.
- 백엔드 컴파일 및 `spotlessCheck` 통과.
- **운영 배포 및 실측 완료**:
- PR #32 (`ec22a61`) `main` 브랜치 머지 완료.
- `Deploy Backend` (36820657756): DB 006 마이그레이션 적용 및 백엔드 배포 성공.
- `Deploy Frontend` (36820657861): 프론트엔드 빌드 및 ECR 푸시, EC2 재기동 성공.
- 운영 환경(`https://snowthing.org`) 실측:
* `https://snowthing.org/vivaldi.html`: HTTP 200 OK (정상 서빙)
* `https://snowthing.org/stream-proxy/high1/1/playlist.m3u8`: HTTP 200 OK (`application/vnd.apple.mpegurl`)
* `https://snowthing.org/stream-proxy/o2/cam0.m3u8`: HTTP 200 OK (`application/vnd.apple.mpegurl`)
* `https://snowthing.org/stream-proxy/muju/01/playlist.m3u8`: HTTP 200 OK (`application/vnd.apple.mpegurl`)
* `https://snowthing.org/api/v1/resort-cams`: 휘닉스파크 7개소 최신 HLS URL 정상 응답
* `https://snowthing.org/resort-cam`: 슬로프캠 뷰어 메인 페이지 200 OK 정상 서빙

- **슬로프캠 백엔드 CI 통과 및 CodeRabbit 피드백 전수 조치 (2026-09-29)**:
- 상태: 구현, 테스트 통과, DB/시드/CI스크립트 동기화 완료
Expand Down Expand Up @@ -1831,3 +1842,43 @@
- `npm run build`로 리조트캠 전체·리조트별·개별 카메라 동적 라우트를 포함한 production build를 통과했습니다.
- Docker MySQL 8 임시 DB에서 `003`·`004`·`005` 마이그레이션 최초 적용과 재실행 이력, 활성 리조트 12개, 카메라 89개, 4개 소스 유형을 검증했습니다. 검증용 DB는 완료 후 삭제했습니다.
- 별도 검증 DB와 백엔드 포트에서 비로그인 `GET /api/v1/resort-cams`가 200으로 리조트 12개와 카메라 89개를 반환하는지 확인하고 검증 환경을 정리했습니다.

## 운영 EC2 디스크 긴급 정리

- 일자: 2026-10-01
- 루트 볼륨이 28GB 중 28GB를 사용해 사용률 99%, 가용 공간 287MB까지 감소한 상태를 확인했습니다.
- Docker 이미지 19.16GB 중 17.52GB, 빌드 캐시 5.519GB 중 4.563GB가 회수 가능한 상태였습니다.
- 실행 중인 컨테이너와 볼륨은 유지하고 `docker image prune -af`, `docker builder prune -af`로 미사용 이미지 17.23GB와 빌드 캐시 4.563GB를 정리했습니다.
- 정리 후 루트 볼륨 사용량은 7.6GB, 가용 공간은 21GB, 사용률은 27%로 회복했습니다.
- `snowthing-backend`, `snowthing-frontend` 컨테이너가 모두 실행 중인 상태를 확인했습니다.
- 현재 ECR 배포 스크립트에는 EC2 로컬 이미지 정리 단계가 없어 재발 가능성이 있습니다. 배포 성공 후 미사용 이미지를 정리하는 정책은 별도 변경 계획으로 반영해야 합니다.

## 댓글 UI 정리

- 일자: 2026-10-01
- 익명 게시판에서도 댓글 영역 제목을 `댓글`로 통일하고, 작성 폼의 익명 아이콘과 익명 표기를 제거했습니다.
- 비회원 댓글·답글 비밀번호 입력란에 명확한 라벨과 `비밀번호를 입력하세요` 안내를 적용했습니다.
- 비밀번호 누락 안내를 `비밀번호를 입력해주세요.`로 단순화했습니다.
- 댓글 작성 폼을 단일 입력 카드로 정리하고 비밀번호와 등록 버튼을 하단 작업 영역에 배치했습니다.
- 대댓글은 들여쓰기, 옅은 배경, 테두리와 답글 아이콘을 적용해 일반 댓글과 계층을 구분했습니다.
- 모바일에서는 비밀번호 입력과 등록 버튼이 세로로 배치되도록 반응형 스타일을 적용했습니다.
- 변경 대상 파일 ESLint와 프런트엔드 production build를 통과했습니다. 전체 ESLint에는 기존 리조트캠 미사용 변수 1건과 `public/vivaldi.js` 경고 43건이 남아 있습니다.

## 중고장터 가격 옵션 UI 수정

- 일자: 2026-10-01
- 가격 행의 공통 입력 스타일이 체크박스에도 적용되어 `가격 협의 가능`, `무료 나눔` 체크박스가 과도하게 커지던 문제를 수정했습니다.
- 가격 입력 스타일의 적용 대상을 숫자 입력란으로 제한하고 체크박스 크기를 `16px`로 고정했습니다.
- 옵션 문구가 불필요하게 줄바꿈되지 않도록 한 줄 정렬을 적용했습니다.
- 판매글 등록과 수정 화면이 같은 스타일을 공유하므로 두 화면에 동일하게 반영됩니다.

## EC2 Docker 이미지 자동 정리

- 일자: 2026-10-01
- 배포 성공과 헬스체크 완료 후 해당 서비스의 오래된 ECR 로컬 이미지를 자동으로 정리하도록 배포 스크립트를 수정했습니다.
- 서비스별 현재 실행 이미지와 직전 배포 이미지 1개를 image ID 기준으로 보호해 즉시 롤백할 수 있도록 했습니다.
- 다른 서비스의 롤백 이미지를 제거할 수 있는 전역 `docker image prune -af` 대신, 현재 배포 대상 ECR 저장소의 오래된 digest만 선별 삭제합니다.
- 사용하지 않는 Docker 빌드 캐시는 배포 성공 후 정리합니다.
- 정리 실패는 경고로 기록하되 이미 헬스체크를 통과한 배포를 실패나 롤백 상태로 바꾸지 않습니다.
- 정리 전후 `docker system df` 결과와 삭제 개수, 보호한 이미지 ID를 배포 로그에 남깁니다.
- Bash 문법 검사와 ShellCheck를 수행했으며, 새 정리 로직에서는 오류가 발견되지 않았습니다. ShellCheck 정보 메시지 4건은 기존 Nginx 설정에 `$` 리터럴을 전달하기 위한 의도된 작은따옴표 사용입니다.
61 changes: 52 additions & 9 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,42 @@ button:disabled {
.post-detail-card > h1 { margin: 12px 0; color: #036e9f; font-size: clamp(22px,3vw,30px); font-weight: 850; letter-spacing: -.04em; line-height: 1.3; }.post-detail-author { display: flex; align-items: center; gap: 10px; border-radius: 10px; background: #f1f3f5; padding: 11px 12px; }.post-detail-author > .material-symbols-outlined { display: grid; width: 32px; height: 32px; place-items: center; border-radius: 50%; background: #0284c7; color: white; font-size: 18px; }.post-detail-author > div:not(.post-detail-owner-actions) { display: flex; flex-direction: column; gap: 2px; }.post-detail-author strong { color: #475569; font-size: 11px; }.post-detail-author small { color: #94a3b8; font-size: 9px; }.post-detail-owner-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }.post-detail-owner-actions a,.post-detail-owner-actions button { border: 0; background: none; color: #64748b; font-size: 10px; font-weight: 750; }.post-detail-owner-actions button { color: #e11d48; }
.post-detail-content { min-height: 220px; padding: 28px 2px; color: #334155; font-size: 14px; line-height: 1.8; }.post-detail-images { display: grid; gap: 12px; }.post-detail-images img { width: 100%; max-height: 720px; border-radius: 10px; object-fit: contain; }.post-detail-reactions { display: flex; flex-direction: column; align-items: center; gap: 9px; border-top: 1px solid #f1f5f9; padding-top: 20px; }.post-detail-reactions > div { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }.post-detail-reactions button { display: inline-flex; min-height: 40px; align-items: center; gap: 6px; border: 0; border-radius: 10px; background: #f1f3f5; padding: 0 13px; color: #64748b; font-size: 11px; font-weight: 750; }.post-detail-reactions button.active { background: #0284c7; color: white; }.post-detail-reactions .material-symbols-outlined { font-size: 16px; }.post-detail-reactions p { margin: 0; }
.post-comments-card { padding: 22px 24px; }.post-comments-card > header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }.post-comments-card > header h2 { margin: 0; color: #036e9f; font-size: 15px; font-weight: 850; }.post-comments-card > header h2 span { display: inline-flex; min-width: 24px; height: 20px; align-items: center; justify-content: center; border-radius: 99px; background: #e0e7ff; color: #64748b; font-family: Inter,sans-serif; font-size: 10px; }
.post-comment-compose { border-radius: 11px; background: #f1f3f5; padding: 13px; }.post-comment-identity { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }.post-comment-identity > span { display: inline-flex; height: 25px; align-items: center; gap: 4px; border-radius: 7px; background: white; padding: 0 8px; color: #036e9f; font-size: 10px; font-weight: 800; }.post-comment-identity .material-symbols-outlined { font-size: 14px; }.post-comment-identity input { width: 105px; height: 25px; border: 0; border-radius: 7px; outline: 0; padding: 0 8px; font-size: 10px; }.post-comment-textarea { width: 100%; min-height: 95px; resize: vertical; border: 0; border-radius: 9px; outline: 0; background: white; padding: 12px; color: #334155; font-size: 12px; }.post-comment-textarea:focus { box-shadow: 0 0 0 2px #0891b2; }.post-comment-submit-row { display: flex; justify-content: flex-end; margin-top: 9px; }.post-comment-submit-row button { display: inline-flex; min-height: 36px; align-items: center; gap: 5px; border: 0; border-radius: 8px; background: #0284c7; padding: 0 14px; color: white; font-size: 11px; font-weight: 800; }.post-comment-submit-row .material-symbols-outlined { font-size: 15px; }.post-comment-login-note { display: block; margin-top: 7px; color: #94a3b8; font-size: 10px; }.post-comment-list { display: grid; gap: 15px; margin-top: 20px; }.post-comments-more { display: grid; justify-items: center; gap: 6px; margin-top: 20px; }.post-comments-more-button { min-height: 38px; border: 0; border-radius: 9px; background: #f1f3f5; padding: 0 18px; color: #64748b; font-size: 11px; font-weight: 750; }
.post-comment-compose { overflow: hidden; border: 1px solid #dbe3ed; border-radius: 10px; background: white; }
.post-comment-textarea { display: block; width: 100%; min-height: 104px; resize: vertical; border: 0; outline: 0; background: transparent; padding: 14px 15px; color: #334155; font-size: 13px; line-height: 1.65; }
.post-comment-textarea::placeholder { color: #94a3b8; }
.post-comment-compose:focus-within,.post-reply-compose:focus-within { border-color: #7dd3fc; box-shadow: 0 0 0 3px rgba(14,165,233,.08); }
.post-comment-compose-footer { display: flex; min-height: 52px; align-items: center; justify-content: flex-end; gap: 10px; border-top: 1px solid #e2e8f0; background: #f8fafc; padding: 8px 9px 8px 13px; }
.post-comment-password { display: flex; min-width: 0; align-items: center; gap: 9px; margin-right: auto; color: #64748b; font-size: 11px; font-weight: 700; }
.post-comment-password input { width: 180px; height: 34px; border: 1px solid #dbe3ed; border-radius: 7px; outline: 0; background: white; padding: 0 10px; color: #334155; font-size: 11px; }
.post-comment-password input:focus { border-color: #38bdf8; }
.post-comment-submit-button { display: inline-flex; min-height: 36px; flex: 0 0 auto; align-items: center; justify-content: center; gap: 5px; border: 0; border-radius: 7px; background: #0f2942; padding: 0 14px; color: white; font-size: 11px; font-weight: 800; }
.post-comment-submit-button:hover { background: #0369a1; }
.post-comment-submit-button .material-symbols-outlined { color: #7dd3fc; font-size: 15px; }
.post-comment-login-note { display: block; margin: 7px 12px; color: #94a3b8; font-size: 10px; }
.post-comment-list { display: grid; margin-top: 22px; }
.post-comment-item { border-bottom: 1px solid #e2e8f0; padding: 0 0 18px; }
.post-comment-item + .post-comment-item { padding-top: 18px; }
.post-comment-reply { margin: 12px 0 0 24px; border: 1px solid #e2e8f0; border-radius: 9px; background: #f8fafc; padding: 14px 16px; }
.post-comment-reply-author { display: flex; min-width: 0; align-items: center; gap: 6px; }
.post-comment-reply-author > .material-symbols-outlined { color: #94a3b8; font-size: 17px; }
.post-reply-compose { overflow: hidden; margin: 14px 0 0 24px; border: 1px solid #dbe3ed; border-radius: 9px; background: white; }
.post-reply-compose .post-comment-textarea { min-height: 82px; }
.post-comments-more { display: grid; justify-items: center; gap: 6px; margin-top: 20px; }.post-comments-more-button { min-height: 38px; border: 0; border-radius: 9px; background: #f1f3f5; padding: 0 18px; color: #64748b; font-size: 11px; font-weight: 750; }
.post-detail-sidebar { display: flex; flex-direction: column; gap: 6px; }.post-detail-sidebar > p { margin: 0; padding-inline: 5px; color: #94a3b8; font-size: 9px; }.detail-carpool-card { padding: 15px; }.detail-carpool-card > header { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #f1f5f9; padding-bottom: 10px; }.detail-carpool-card h2 { display: flex; align-items: center; gap: 5px; margin: 0; color: #036e9f; font-size: 12px; }.detail-carpool-card h2 .material-symbols-outlined { font-size: 16px; }.detail-carpool-card header small { color: #94a3b8; font-size: 9px; }.detail-carpool-card article { border-bottom: 1px solid #f1f5f9; padding-block: 12px; }.detail-carpool-card article:last-child { border-bottom: 0; padding-bottom: 2px; }.detail-carpool-card article div,.detail-carpool-card article p { display: flex; align-items: center; justify-content: space-between; gap: 8px; }.detail-carpool-card article div b { border-radius: 4px; background: #475569; padding: 3px 6px; color: white; font-size: 8px; }.detail-carpool-card time { color: #94a3b8; font-size: 9px; }.detail-carpool-card article > strong { display: block; margin-top: 8px; color: #036e9f; font-size: 10px; }.detail-carpool-card article > strong span { color: #94a3b8; }.detail-carpool-card article p { margin: 6px 0 0; color: #94a3b8; font-size: 9px; }.detail-carpool-card article p b { color: #334155; }

@media (max-width: 960px) { .post-detail-layout { grid-template-columns: 1fr; }.post-detail-sidebar { order: 2; } }
@media (max-width: 640px) { .post-detail-page { padding-block: 12px 32px; }.post-detail-card,.post-comments-card { padding: 17px 14px; }.post-detail-card > h1 { font-size: 21px; }.post-detail-meta-top > div { gap: 3px; }.post-detail-content { min-height: 160px; padding-block: 22px; }.post-comment-compose { padding: 10px; } }
@media (max-width: 640px) {
.post-detail-page { padding-block: 12px 32px; }
.post-detail-card,.post-comments-card { padding: 17px 14px; }
.post-detail-card > h1 { font-size: 21px; }
.post-detail-meta-top > div { gap: 3px; }
.post-detail-content { min-height: 160px; padding-block: 22px; }
.post-comment-compose-footer { align-items: stretch; flex-direction: column; }
.post-comment-password { width: 100%; }
.post-comment-password input { min-width: 0; flex: 1; width: auto; }
.post-comment-submit-button { width: 100%; }
.post-comment-reply,.post-reply-compose { margin-left: 12px; }
}

/* Shared board list */
.board-page { min-height: calc(100vh - 142px); padding-block: 12px 48px; }
Expand Down Expand Up @@ -898,14 +929,25 @@ html[data-theme="dark"] .pagination button.current {
html[data-theme="dark"] .compose-title-input,
html[data-theme="dark"] .compose-image-input,
html[data-theme="dark"] .compose-password-field input,
html[data-theme="dark"] .post-comment-identity > span,
html[data-theme="dark"] .post-comment-identity input,
html[data-theme="dark"] .post-comment-password input,
html[data-theme="dark"] .post-comment-textarea {
border-color: var(--snow-border);
background: var(--snow-surface-mid);
color: var(--snow-ink);
}

html[data-theme="dark"] .post-comment-compose,
html[data-theme="dark"] .post-reply-compose,
html[data-theme="dark"] .post-comment-reply,
html[data-theme="dark"] .post-comment-compose-footer {
border-color: var(--snow-border);
background: var(--snow-surface-low);
}

html[data-theme="dark"] .post-comment-item {
border-color: var(--snow-border);
}

html[data-theme="dark"] .compose-title-input:focus,
html[data-theme="dark"] .compose-image-input:focus,
html[data-theme="dark"] .compose-password-field input:focus {
Expand Down Expand Up @@ -979,7 +1021,7 @@ html[data-theme="dark"] .board-write-button,
html[data-theme="dark"] .board-category-tabs button.active,
html[data-theme="dark"] .board-pagination button.current,
html[data-theme="dark"] .post-detail-reactions button.active,
html[data-theme="dark"] .post-comment-submit-row button {
html[data-theme="dark"] .post-comment-submit-button {
border-color: #1e5b84;
background: #1e5b84;
color: white;
Expand Down Expand Up @@ -1174,8 +1216,8 @@ html[data-theme="dark"] .market-results.album .market-result-card { border-color
.market-compose-card { display: flex; flex-direction: column; gap: 22px; border: 1px solid var(--snow-border); border-radius: 13px; background: var(--snow-canvas); padding: 26px; box-shadow: var(--community-card-shadow); }
.market-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.market-form-grid label,.market-form-field,.market-price-row > label:first-child { display: flex; flex-direction: column; gap: 7px; color: var(--snow-ink); font-size: 12px; font-weight: 700; }
.market-form-grid select,.market-form-field input,.market-price-row input { height: 44px; border: 1px solid var(--snow-border-strong); border-radius: 8px; outline: 0; background: var(--snow-background); padding: 0 12px; color: var(--snow-ink); font: inherit; font-weight: 500; }
.market-form-grid select:focus,.market-form-field input:focus,.market-price-row input:focus { border-color: var(--alpine-sky); box-shadow: 0 0 0 3px rgba(2,132,199,.1); }
.market-form-grid select,.market-form-field input,.market-price-row > label:first-child input { height: 44px; border: 1px solid var(--snow-border-strong); border-radius: 8px; outline: 0; background: var(--snow-background); padding: 0 12px; color: var(--snow-ink); font: inherit; font-weight: 500; }
.market-form-grid select:focus,.market-form-field input:focus,.market-price-row > label:first-child input:focus { border-color: var(--alpine-sky); box-shadow: 0 0 0 3px rgba(2,132,199,.1); }
.market-form-field small,.market-editor-field p,.market-image-field small { color: var(--snow-muted); font-weight: 500; }
.market-radio-field { display: flex; align-items: center; gap: 20px; border: 0; padding: 0; }
.market-radio-field legend { float: left; min-width: 82px; color: var(--snow-ink); font-size: 12px; font-weight: 700; }
Expand All @@ -1184,9 +1226,10 @@ html[data-theme="dark"] .market-results.album .market-result-card { border-color
.market-price-row { display: flex; align-items: flex-end; gap: 18px; }
.market-price-row > label:first-child { flex: 1; }
.market-price-row > label:first-child > div { position: relative; }
.market-price-row input { width: 100%; padding-right: 38px; }
.market-price-row > label:first-child input { width: 100%; padding-right: 38px; }
.market-price-row b { position: absolute; top: 50%; right: 13px; color: var(--snow-muted); font-size: 12px; transform: translateY(-50%); }
.market-check { height: 44px; }
.market-check { height: 44px; cursor: pointer; white-space: nowrap; }
.market-check input { flex: 0 0 16px; width: 16px; height: 16px; margin: 0; padding: 0; cursor: pointer; }
.market-editor-field > span,.market-image-field > div > span { color: var(--snow-ink); font-size: 12px; font-weight: 700; }
.market-editor-field p { margin: 5px 0 9px; font-size: 11px; }
.market-image-field > div { display: flex; justify-content: space-between; margin-bottom: 8px; }
Expand Down
Loading
Loading