28 KiB
설계 기록
이 문서는 구현 전에 정리한 설계 기록입니다. 모두 구현되었습니다. 현재 동작과 설정은 README와 configuration.md를 보세요. 구현하면서 바뀐 점은 아래와 같습니다.
- 이름과 로고의 기본값:
Dodo Transfer와 임의로 만든 도도새 아이콘(public/assets/logo.svg)입니다. AJIN Transfer와 아진산업 로고(docs/brand/ajin-logo.png)는 관리자 설정이나 환경변수PSITRANSFER_APP_NAME으로 쓸 수 있습니다. (항목 2, 12)- PDF 썸네일: pdf.js 약 1.7MB(워커 포함), 글꼴과 CMap 데이터 약 2.3MB가 PDF를 고를 때만 내려받아집니다. (항목 6)
docs/mockup/index.html은 디자인을 정할 때 쓴 목업입니다 (AJIN 로고와 이름 기준).- 업로드 인증의
x-passwd원문 헤더는 스크립트용으로 계속 받습니다. (항목 1)
PsiTransfer(BSD-2-Clause) 기반 포크.
상태 표기: 확정 / 보류 / 최종 단계
1. 업로드 인증 토큰 — 확정
업로드 비밀번호(uploadPass)를 매번 입력하지 않도록 서버 발급 토큰을 localStorage에 저장한다.
- 방식: 서버 발급 서명 토큰 (
base64url({exp}).HMAC), 서버 저장 상태 없음 - 로그인:
POST /login→{ token, exp }. 실패 시 지연 후 403 (무차별 대입 완화) - 요청 헤더:
Authorization: Bearer <token>. 검증은 상수 시간 비교 - 유효 기간: 7일, 사용할 때마다 연장 (접속하여
config.json을 받을 때 새 토큰 발급, 업로드 청크마다는 갱신하지 않음). 기간은 설정 항목으로 변경 가능 - 서명 키:
adminPass와uploadPass를 재료로 파생 → 둘 중 하나를 바꾸면 기존 토큰 전부 무효.adminPass가 없으면uploadPass만 사용. 환경변수PSITRANSFER_TOKEN_SECRET이 있으면 그 값 우선 - 도커: 키가 설정에서 파생되므로 볼륨 없이 재시작/재생성 후에도 토큰 유지
- 범위: 업로드만.
/admin은 현행 유지. 로그아웃 없음 - 화면: 저장된 토큰으로 먼저
config.json요청 → 성공하면 로그인 화면 생략. 만료/불일치면 토큰 삭제 후 로그인 화면. localStorage 사용 불가 시 메모리에만 보관 - 영향 파일:
lib/endpoints.js(인증부),config.js(설정 추가),app/src/Upload/store/config.js,app/src/Upload.vue - 구현 직전 확인할 것: 기존
x-passwd원문 헤더를 curl/스크립트용으로 계속 받을지 여부 - 보류: 다운로드 bucket 비밀번호는 나중에 논의
2. 브랜딩 — 확정
- 이름:
AJIN Transfer. 한국어 문장 안에서도 영문 그대로 사용 - 바꿀 곳:
<title>(head.pug),<h1>(upload.pug,download.pug,error.pug,admin.pug), 푸터, 메일 제목(lang/*.js의mailSubject*), 콘솔 로그(app.js),package.json - 푸터:
© 2026–{현재연도} 아진산업(주) 전장선행개발팀(기존 "Powered by PsiTransfer" 링크 제거). 연도는 서버가 렌더링할 때 계산하고, 현재 연도가 2026이면© 2026만 표기 - 로고: 사용자가 제공한 이미지(256×256 PNG, 투명 배경, 노란색
#FCB813원형 엠블럼)를 사용. 인라인 SVG 대신public/assets/logo.png를<img>로 참조 → 교체 시 파일만 덮어쓰면 됨. 파비콘도 이 이미지에서 생성. SVG 원본이 생기면 교체 - 회사 색상: 노란색 (
#FCB813). 로고에만 사용한다. 화면 테마(버튼, 진행바 등)는 노란색으로 바꾸지 않음 - 위 이름/로고/푸터는 기본값이고, 관리자 설정에서 바꿀 수 있음 (항목 12). 사용자의 로컬 서버 등 다른 환경에서도 같은 이미지를 쓰기 위함
- 환경변수 접두사
PSITRANSFER_*는 유지 (도커 설정 호환) - 라이선스:
LICENSE와 원 저작권 고지 유지. "PsiTransfer 기반" 출처는README.md/LICENSE에 표기
5. 업로드 설정: 보관 기간 버튼, 비밀번호 필수 조건 — 확정
- 보관 기간: 자주 쓰는 몇 개는 버튼, 나머지는 리스트박스(
select) 로 혼합. 사용자 결정.- 버튼(확정): 1회 다운로드 / 1일 / 3일 / 5일 / 7일. 나머지(1시간, 6시간, 2주, 4주, 8주)는 "그 외" 리스트박스
- 기본
retentions에432000(5일)이 없으므로 추가하고,604800의 라벨을 "1주일"에서 "7일"로 통일(lang라벨 포함). 기본 선택은 7일 유지 - 버튼에 올릴 키는 설정
quickRetentions(기본one-time,86400,604800, 환경변수PSITRANSFER_QUICK_RETENTIONS)로 지정 → 코드 수정 없이 변경. 리스트박스에는 버튼에 없는retentions만 나옴 - 하나만 선택됨. 리스트박스에서 고르면 버튼 선택은 해제되고 리스트박스가 선택된 값을 표시(활성 상태로 강조). 버튼 그룹은 라디오 그룹 의미로 접근성 유지
- 선택지 목록은 서버 설정(
retentions)에서 받아 동적으로 그림. 라벨은lang.retentions[키]우선, 없으면 설정의 라벨. 기본 선택은defaultRetention(1주일). 1회 다운로드는 시각적으로 구분 defaultRetention이나quickRetentions가retentions에 없는 값이면 서버 시작 시 경고하고 무시
- 비밀번호 필수 조건:
requireBucketPassword가 켜져 있어도 1회 다운로드를 고르면 비밀번호 없이 업로드 가능. 그 외 보관 기간은 필수 (해석 확인 필요: "다 필수가 아니고 1회 다운로드면 없어도 됨" = 기본은 필수, 1회 다운로드만 예외) - 서버 검증 추가: 현재
requireBucketPassword는 프론트에서만 검사하고 서버는 확인하지 않음. 서버 POST 처리(endpoints.js)에서도requireBucketPassword && retention !== 'one-time' && !password이면 400 거부 - 화면: 1회 다운로드 선택 시 비밀번호 칸은 숨기지 않고 "선택 사항"으로 표시, 입력된 값은 유지. 랜덤 비밀번호 생성 버튼 유지
- 영향 파일:
Settings.vue(→ 새 컴포넌트),Upload.vue의showUploadBtn조건,lib/endpoints.js(검증),lang/en.js·ko.js(필요 시 문구) - 영향 파일 추가:
config.js(quickRetentions),config.json응답에 포함(endpoints.js) - 비밀번호 필수 조건 (확정): 기본은 필수, 1회 다운로드만 예외
- 인증된 사용자는 비밀번호 없이 다운로드 (체크박스) — 사용자 제안, 이 방식으로 진행:
- 업로드 화면에 체크박스 하나. bucket 비밀번호는 그대로 설정하고(필수 조건 그대로 적용), 체크하면 추가로 "사내 인증자는 비밀번호 면제"가 됨
- 다운로드 링크를 열었을 때: 브라우저에 유효한 세션(항목 1의 업로드 인증 토큰)이 있으면 비밀번호 입력을 건너뜀. 세션이 없거나 만료됐으면 지금처럼 bucket 비밀번호를 입력해야 함 (외부 수신자는 이쪽)
- 구현: 체크 상태를 파일 메타데이터
internalBypass로 저장(tus 메타데이터는 문자열만 →"true"). 다운로드 목록 응답({sid}.json)에서Authorization: Bearer토큰이 유효하고internalBypass가 켜진 bucket이면 비밀번호 검사를 생략. 다운로드 페이지는 같은 출처라 localStorage 토큰을 그대로 읽어 헤더에 실음 - 로그인 화면은 다운로드 페이지에 두지 않음 (세션이 없으면 비밀번호 입력으로 대체)
- 체크박스는 비밀번호가 입력된 경우에만 활성화 (비밀번호가 없는 1회 다운로드는 해당 없음).
uploadPass가 꺼져 있어 세션 자체가 없는 환경에서는 숨김 - 비밀번호 원문을 bucket 비밀번호로 재사용하는 안은 채택하지 않음 (토큰 방식이라 클라이언트에 원문이 없고, 외부 수신자에게 업로드 비밀번호가 노출됨)
- 파일 URL(
/files/{sid}++{uuid})은 현재도 비밀번호 검사를 하지 않고 UUID로 보호됨 → 같은 모델 유지. 검사는 목록({sid}.json)에서만 - 다운로드 페이지에서 유효한 토큰으로 접속하면 항목 1의 슬라이딩 연장도 같이 적용(응답으로 새 토큰 발급)
- 체크박스 기본값: 켬. 문구: "인증된 사용자는 비밀번호 없이 다운로드" (확정). 비밀번호 칸이 비어 있으면 비활성화
- 항목 5 확정 (미결 없음)
6. 다운로드 화면 미리보기 확대 — 확정
현재: 눈 모양 버튼으로 모달을 열어 이미지와 텍스트만, 2MB 이하, 1회 다운로드 파일은 제외. 파일은 Content-Disposition: attachment로 내려가고
저장 파일에 확장자가 없어 Content-Type이 application/octet-stream이라 이미지 외에는 브라우저 내장 뷰어가 동작하지 않음.
- 미리보기 대상 (제안): 이미지(png/jpg/gif/webp/avif/bmp/svg), PDF, 동영상(mp4/webm), 오디오(mp3/wav/ogg/m4a), 텍스트·코드(기존 확장자 목록 유지, 필요 시 확장)
- 서버: 미리보기용 요청(
?inline=1)에 한해Content-Disposition: inline으로 내려줌.Content-Type은 파일 이름의 확장자로 서버가 결정한 허용 목록 값만 사용. 클라이언트가 보낸metadata.type은 신뢰하지 않음 (위조 가능)X-Content-Type-Options: nosniff, 응답 CSPsandbox적용 → 업로드된 HTML/SVG 안의 스크립트 실행 방지. HTML은 허용 목록에서 제외, SVG는<img>로만 표시- 허용 목록에 없는 형식은 기존처럼 attachment(다운로드)
- 동영상/오디오 탐색(seek)을 위해 Range 요청 지원 (
res.sendFile이 이미 지원)
- 동영상/오디오는 코덱에 따라 재생 여부가 다름 (확장자만으로 알 수 없음). 확장자가
mp4/m4v/mov/webm/ogv(오디오는mp3/wav/ogg/m4a/flac)이면 미리보기 버튼을 표시하고, 재생할 수 없으면(브라우저의 오류 이벤트) 모달 안에 "이 브라우저에서 재생할 수 없는 형식입니다. 다운로드해서 확인하세요." 안내와 다운로드 버튼을 표시. 썸네일은 업로드한 브라우저가 디코딩할 수 있을 때만 생성되므로, 썸네일이 있어도 받는 PC에서는 재생이 안 될 수 있음. 서버 변환(ffmpeg)은 도입하지 않음 - 미리보기는 다운로드로 집계하지 않음:
lastDownload갱신과fileDownloaded웹훅은inline요청에서 제외 - 1회 다운로드 파일은 미리보기 불가 유지 (미리보기가 곧 소모가 되기 때문)
- 크기 제한: 텍스트는 메모리에 올리므로
maxPreviewSize(2MB) 유지. 이미지·PDF·동영상·오디오는 스트리밍이라 크기 제한을 두지 않음 (또는 별도 설정) - UI: 라이트박스 형태 모달, 이전/다음(키보드 ←/→), 닫기(Esc), 다운로드 버튼, 파일 이름과 순번. 이미지 파일은 목록에서 미리보기 가능 표시
- 영향 파일:
lib/endpoints.js(inline 처리, 허용 목록), 새lib/mime.js(확장자→타입 허용 목록),PreviewModal.vue(재작성),Download.vue - 범위 결정 (사용자): 브라우저가 자체적으로 표시할 수 있는 형식만 미리보기. Office 문서/CAD 등은 서버 변환 없이 미리보기 제외(다운로드만)
- 썸네일 (사용자 요청으로 변경): 목록의 파일 종류 아이콘 자리에, 미리보기가 되는 파일은 누르지 않아도 보이는 작은 썸네일을 표시 (이전의 "썸네일 없음" 결정을 대체).
확장자 표시는 썸네일 왼쪽 아래 작은 뱃지, 눈 모양 뱃지가 있는 썸네일/아이콘을 누르면 미리보기 모달. 썸네일이 없는 형식은 지금처럼 색 아이콘
- 생성 위치: 업로드하는 브라우저가 만듦 (서버에 이미지 처리 라이브러리를 추가하지 않음). 이미지는 캔버스로 약 104px 축소(webp), 텍스트는 첫 줄들을 그림으로 변환
- 전달: 파일 업로드가 끝나면 작은 썸네일(최대 64KB)을 서버에 올림 (
/files/{sid}++{key}.thumb). 서버는 크기와 파일 시그니처(webp/png/jpeg)만 검증하고X-Content-Type-Options: nosniff로 이미지 형식으로만 내려줌. 파일 삭제/만료 시 썸네일도 함께 삭제 (lib/store.js의 삭제 대상에 추가) - 업로드 화면: 파일을 고르면 즉시 썸네일 표시 (올리기 전에 확인 가능, 클릭하면 실제 파일을 미리보기)
- 다운로드 화면/관리자 화면: 서버에 저장된 썸네일을 표시. API(curl 등)로 올려 썸네일이 없는 파일은 아이콘으로 대체
- 1회 다운로드 파일은 썸네일도 표시하지 않음 (미리보기 불가 원칙과 일치)
- PDF 첫 장 썸네일 (확정):
pdf.js를 프론트에 포함(약 1MB, Apache-2.0이라 라이선스 고지 파일 포함). 외부 CDN을 쓰지 않고public/app에 함께 배포. PDF를 고른 경우에만 늦게 불러오므로(lazy load) PDF를 안 쓰면 추가 다운로드 없음. 첫 장을 작게 그려 webp로 변환. 안전장치: 50MB 초과 PDF, 암호가 걸린 PDF, 읽기 실패나 5초 초과 시 썸네일을 포기하고 아이콘으로 표시 (업로드 자체에는 영향 없음) - 동영상 첫 프레임 썸네일 (기본 포함으로 제안): 브라우저
<video>로 첫 프레임을 캔버스에 그려 변환. 라이브러리 불필요. 이의가 없으면 포함 - 목업에는 이미지와 텍스트 썸네일만 실제로 생성되고, PDF와 동영상은 샘플 그림으로 대체되어 있음 (구현 단계에서 실제 생성)
- 항목 6 확정. 미리보기는 버튼으로 열고, 지원하는 형식의 파일에만 버튼을 표시. 새 창은 쓰지 않음 (항목 8)
7. 업로드 화면 흐름 — 확정 (모바일 처리는 제안)
- 화면을 상단 고정 영역(스크롤되지 않음) 과 파일 목록 영역으로 나눔. 파일이 늘면 목록 영역만 아래로 길어지고 스크롤됨
- 상단 고정 영역은 세로 공간을 최소화하도록 두 줄로 압축 (목업 검토 후 수정, 약 160px):
1줄:
파일 추가· 요약(파일 수, 크기, 보관 기간) · 필요한 경우 경고 · 업로드 버튼(오른쪽 끝) 2줄: 왼쪽 보관 기간(라벨 옆에 버튼 + 그 외 리스트박스), 오른쪽 비밀번호 입력 + 생성 버튼, 그 아래 "인증된 사용자는 비밀번호 없이 다운로드" 체크박스. 비밀번호 필수/선택 표시는 별도 태그 대신 입력창 안내 문구(비밀번호 (필수)/비밀번호 (선택))로 표시. 비밀번호 입력창 오른쪽 안에 지우기(X) 버튼 (값이 있을 때만 표시). 지우면 입력창에 포커스가 가고 체크박스는 비활성화, 필수 보관 기간이면 업로드 버튼이 막히고 "비밀번호를 입력하세요" 표시 업로드 버튼이 파일 목록 위에 있어서 파일이 아무리 많아도 항상 보임 - 파일 목록 영역: 비어 있으면 큰 드롭 안내, 파일이 있으면 이름/크기/코멘트/진행바/삭제. 화면 어디에 놓아도 파일 추가
- 드래그앤드롭을 알아볼 수 있게 (사용자 지적: 버튼만 있으면 드롭이 되는지 알 수 없음):
- 파일이 있을 때도 목록 카드 아래에 항상 보이는 점선 드롭 영역 "파일을 여기로 끌어다 놓거나 클릭해서 추가" (클릭하면 파일 선택, 목록 스크롤과 무관하게 고정)
- 파일을 끌고 화면에 들어오면 전체 화면 오버레이 "여기에 놓아 파일 추가" 표시 (놓으면 추가, 벗어나면 사라짐)
- Ctrl+V 붙여넣기로 클립보드 이미지/파일 추가 (이미지는
붙여넣기_시분초.png로 이름 부여) - 업로드 중/완료 후에는 파일 추가 불가 → 드롭하면 "업로드 중에는 파일을 추가할 수 없습니다" 안내
- 터치 기기(드래그 불가)에서는 점선 영역을 숨기고
파일 추가버튼만 사용
- 업로드 중: 상단 고정 영역에 전체 진행률 표시, 개별 진행률은 목록의 각 파일에 표시. 업로드 중 파일 추가는 지금처럼 불가
- 완료: 상단 고정 영역이 공유 영역(항목 9)으로 바뀜. 목록은 결과로 남음
- 모바일(제안): 상단 영역이 길어 화면을 가리므로 좁은 화면에서는 설정을 접고 펼 수 있는 패널로 두고 업로드 버튼만 고정
8. 다운로드 화면 — 확정
- 파일마다 버튼: 미리보기(지원 형식에만 표시), 다운로드, 링크 복사. 행 클릭은 아무 동작도 하지 않음 (버튼만 사용. 실수로 다운로드되는 것 방지)
- 새 창 금지: 미리보기는 페이지 안 모달. 현재 업로드 완료 후 QR 코드가
window.open으로 새 탭에 열리는데 이것도 페이지 안 모달로 변경. 다운로드는 같은 탭에서 파일 저장(<a download>), zip/tar.gz 전체 다운로드는 현행 유지(페이지 이동 없이 저장됨) - 만료일 표시: 서버가
{sid}.json응답에 bucket 기준 가장 이른 만료 시각과 파일별 만료 시각(expiresAt)을 포함. 계산은lib/db.js의 만료 정리(gc)와 같은 함수를 공유해 화면과 실제 삭제 시점이 어긋나지 않게 함. 표시:2026-11-12 18:30 만료 (7일 남음), 1회 다운로드는 "1회 다운로드 후 삭제됩니다". 24시간 이내면 강조. 날짜는 브라우저 시간대로 표시
9. 업로드 완료 후 공유 — 확정
- "링크와 비밀번호 복사" 버튼 하나 추가 (비밀번호가 있을 때만). 복사 내용 (제안):
AJIN Transfer 파일 공유/다운로드 링크: {URL}/비밀번호: {PW}/만료: {날짜} - 기존 QR 코드, 이메일, 링크 복사 버튼은 유지. 이메일 본문에는 비밀번호를 넣지 않음 (링크와 비밀번호를 같은 메일로 보내면 비밀번호의 의미가 줄어듦)
10. 내 업로드 목록 — 확정 (브라우저 로컬 저장)
사내 SSO가 없어 개인 식별은 하지 않음. 서버 변경 없이 이 브라우저의 localStorage에만 저장. (사용자 결정)
- 업로드가 완료되면 항목을 기록: sid, 링크, 파일 이름 목록, 총 크기, 생성 시각, 보관 기간, 만료 시각, 비밀번호 여부
- 비밀번호 원문은 저장하지 않음 (localStorage에 평문 비밀번호를 남기지 않기 위함). 따라서 목록에서의 복사는 링크만
- 위치: 업로드 화면 상단의 "내 업로드" 버튼 → 페이지 안 모달/패널(새 창 아님)
- 각 항목: 링크 복사, 열기(같은 탭), 목록에서 제거. 최신순
- 상태 표시: 저장된 만료 시각이 지났으면 "만료됨". 아직 유효해 보이는 항목은
GET /{sid}.json응답 코드로 확인 (404면 "삭제됨/만료됨", 401·200이면 존재)해서 서버에서 이미 사라진 것(1회 다운로드 소진, 관리자 삭제)을 반영. 확인 요청에 비밀번호는 필요 없음 - 정리: 만료된 항목은 30일 뒤 자동 제거, 최대 100개 유지
- localStorage를 쓸 수 없는 환경에서는 목록 기능을 숨김
- 한계(화면에 안내 문구로 표시): 이 브라우저에서 올린 것만 보이고, 다른 PC나 브라우저 데이터 삭제 시 사라짐
- 관리자 화면의 업로더 표시는 하지 않음 (식별 불가). 항목 11의 "식별 가능 시 업로더"는 해당 없음
11. 관리자 페이지 — 확정 (인증 방식은 현행 유지)
사람들이 올린 것을 보고 관리: 조회, 다운로드, 삭제.
- 목록: bucket → 파일. 생성일, 마지막 다운로드, 만료, 크기, 비밀번호 여부. 검색, 정렬, 전체 사용 용량 표시 (업로더는 식별 불가라 표시하지 않음)
- 다운로드: 파일 단위, bucket zip. 관리자 권한이므로 bucket 비밀번호 없이 받을 수 있음. 관리자 다운로드는 1회 다운로드 파일을 소모하지 않고, 마지막 다운로드 시각 갱신과 웹훅도 하지 않음
- 브라우저 링크로 받기 위해 헤더 인증 대신 60초짜리 서명 URL을 발급(
POST /admin/download-link). (<a href>는x-passwd헤더를 실을 수 없음) - 삭제: 파일 단위, bucket 단위. 페이지 안 확인 모달 후 파일과 메타데이터를 즉시 삭제. 복구 불가
- 파일 하나를 지우면 받는 사람 화면에서도 즉시 사라짐 (새로 열면 남은 파일만 표시, 마지막 파일을 지우면 bucket이 사라져 "링크를 찾을 수 없음")
- 확인 모달에 "받는 사람 화면에서도 즉시 사라집니다"를 명시
- 삭제 전에 이미 페이지를 열어 둔 받는 사람이 지워진 파일을 누르면 "삭제되었거나 만료된 파일입니다" 안내를 보여주고 목록을 새로 불러옴. 전체 받기 주소는 파일 목록으로 계산되므로 삭제 후 이전 주소는 거부됨 (같은 안내 + 새로고침)
- 미리보기: 다운로드 화면과 같은 모달 재사용
- 현재 비밀번호가 걸린 bucket은 관리자 목록에서 key/url을 숨기는데, 이 동작을 제거 (관리자는 모두 접근)
- 인증: 현행 유지 (항목 1은 업로드만 대상). 비밀번호 비교만 상수 시간 비교로 변경
- 화면은 "파일 관리" / "설정" 두 탭 (설정은 항목 12)
- 영향 파일:
lib/endpoints.js에서 관리자 라우트를lib/admin.js로 분리,Admin.vue재작성
12. 서비스 이름·로고 관리자 설정 — 제안 (사용자 요청, 확정 대기)
사용자의 로컬 서버 등 여러 곳에서 쓸 수 있도록 AJIN Transfer와 로고를 관리자 설정에서 바꿀 수 있게 한다.
- 설정 항목: 서비스 이름(1~40자), 로고 이미지, 하단 저작권 표기(소유자 문구 + 시작 연도, 현재 연도가 크면
2026–현재범위로 자동 표시) - 저장:
/data볼륨 안의 숨김 폴더(/data/.settings/settings.json, 로고 파일). 별도 볼륨 없이 컨테이너를 다시 만들어도 유지.lib/db.js가/data아래 폴더를 bucket으로 읽으므로 이 폴더는 import에서 명시적으로 제외 - 우선순위: 관리자 설정 > 환경변수/설정 파일 기본값(
PSITRANSFER_APP_NAME등) > 내장 기본값(AJIN Transfer, 번들 로고, 항목 2의 푸터) - 적용 범위: 상단 로고와 이름, 브라우저 탭 제목과 파비콘, 푸터, 오류 페이지, 메일 제목(
lang의mailSubject*를{appName}치환으로 변경), "링크와 비밀번호 복사" 문구, 서버 콘솔 로그 - 로고 규칙: PNG / SVG / WebP, 최대 1MB. 확장자가 아니라 파일 시그니처(매직 바이트)로 형식 검증. 정사각형 권장. "기본 로고로 되돌리기" 제공
- 보안: 관리자만 변경 가능. SVG는
<img>로만 사용하고 응답에Content-Security-Policy: sandbox,X-Content-Type-Options: nosniff적용. 이름은 항상 이스케이프 - 서빙:
GET /assets/brand-logo(설정 로고 또는 기본 로고, 내용 해시로?v=캐시 갱신) 하나로 헤더 로고와 파비콘 모두 사용 → 로고를 바꿔도 캐시 때문에 안 바뀌는 문제 방지 - API:
GET /brand.json(공개: 이름, 로고 주소, 푸터),PUT /admin/settings,POST /admin/settings/logo(이미지 본문),DELETE /admin/settings/logo(기본값 복원) - 첫 화면 깜빡임 방지: pug 껍데기에서 이름/제목/푸터를 서버가 직접 채우고 SPA는 그 값을 읽음
- 관리자 비밀번호(
adminPass)가 꺼져 있으면 관리자 페이지가 없으므로 환경변수 기본값만 사용 - 저장 전 미리보기와 "되돌리기"는 목업에 반영됨 (설정 탭)
- 영향 파일:
lib/admin.js(신규), 새lib/settings.js,lib/db.js(import 제외),config.js,public/pug/*.pug,lang/*.js, 프론트 관리자 화면
4. 프론트엔드 플랫폼과 디자인 방향 — 제안 (확정 대기)
사용자가 Vue 3로 바꿔도 된다고 했고, 최근의 예쁜 디자인으로 바꾸길 원함. 서버 API는 항목 1 외에는 바꾸지 않는다.
기술 스택 (제안):
| 영역 | 현재 | 변경 |
|---|---|---|
| 프레임워크 | Vue 2.7 (EOL) | Vue 3 (<script setup>) |
| 빌드 | webpack 5 + vue-loader 15 + babel | Vite |
| 상태 관리 | Vuex 3 | Pinia |
| 템플릿 | <template lang="pug"> |
일반 HTML 템플릿 |
| 스타일 | Bootstrap 3 (styles.css) |
직접 작성한 CSS + 디자인 토큰(CSS 변수). 프레임워크 없음 |
| 아이콘 | vue-awesome | lucide (필요한 것만 번들) |
| 업로드 | tus-js-client 2 | tus-js-client 최신 |
| 드래그앤드롭 | drag-drop 패키지 | 브라우저 기본 API |
| QR | @nuintun/qrcode | 유지 |
| 대상 브라우저 | IE11 포함 | 최신 브라우저만 (IE 폴리필 제거) |
| 언어 | JavaScript | JavaScript 유지 |
유지하는 것:
- 빌드 결과 위치(
public/app)와 파일 이름(common.js,upload.js,download.js,admin.js)을 그대로 두면 pug 껍데기와 Dockerfile의 멀티스테이지 빌드(frontend단계)는 구조 변경이 없음. 이미지 빌드 중에 프론트가 빌드되므로 도커 친화적 - 서버가
lang.json,config.json,{sid}.json으로 주는 계약
디자인 방향 (제안):
- 카드 기반의 깔끔한 레이아웃, 둥근 모서리, 넉넉한 여백, 큰 드롭존
- 노란색은 로고에만. UI 강조색은 중립적인 색(예: 슬레이트/인디고 계열 하나)
- 라이트/다크 모드 (
prefers-color-scheme), 모바일 우선 반응형 - 한글 가독성을 위한 시스템 폰트 스택 (
Pretendard등 웹폰트는 오프라인 여부 확인 후 결정) - 접근성: 키보드 조작, 포커스 표시, 충분한 대비
- Admin 페이지도 같은 스택과 스타일로 통일
진행 순서:
- 화면 흐름(업로드/다운로드 순서) 확정 — 다음 항목들에서 논의
- 흐름이 정해지면 업로드 화면 목업을 HTML로 보여주고 디자인 확인
- 확정 후 구현
결정 (사용자 답변 반영):
- 서버는 외부 인터넷 제한이 없지만 nginx에서 외부 접근을 막을 예정 → 사내망 사용. 외부 CDN/웹폰트에 의존하지 않음. 폰트는 시스템 폰트 스택
(
Pretendard가 설치돼 있으면 사용, 없으면 맑은 고딕/Segoe UI 등) - 강조색은 사용자 선호 없음 → 인디고 계열(
#3552d6, 다크#4d69ee)로 시작하고 목업을 보고 사용자가 수정. 노란색은 로고에만 - 목업:
mockup/index.html(업로드 4상태, 다운로드 5상태, 관리자 파일 관리/설정, 라이트/다크, 모바일). 사용자 검토 후 수정
3. 새 업로드 버튼 확인 팝업 — 확정
- 업로드 진행 중(
uploading)일 때만 확인 팝업, 그 외 상태(new,uploaded,uploadError)는 확인 없이 바로 새 세션 - 새 세션은 지금처럼
document.location.reload()유지 (항목 1의 토큰 덕분에 로그인 화면 없음) - 새 언어 키
cancelUploadConfirm추가 (en.js,ko.js만. 나머지 언어는 영어 기본값 병합으로 대체)- ko:
업로드가 진행 중입니다. 새로 시작하면 진행 중인 업로드가 중단됩니다. 계속할까요? - en:
An upload is in progress. Starting a new one will cancel it. Continue?
- ko:
- 기존
createNewUploadSession키는 삭제하지 않고 둠
최종 단계: 번역 정리 (잊지 말 것)
다른 항목이 모두 확정된 뒤, 최종 화면과 문구에 맞춰 lang/ko.js(필요 시 en.js)의 오타와 의미가 불분명한 번역을 일괄 정리한다.
화면이 바뀌면 문구도 바뀌므로 마지막에 한 번에 한다.
알려진 문제 (현재 시점):
| 키 | 현재 | 문제 |
|---|---|---|
login |
로드인 | 오타 (로그인) |
generateRandomPassword |
렌덤 비밀번호를 생성하기 | 오타, 어색한 어미 |
downloadLink |
링크 다운로드하기 | 뜻이 뒤집힘 (다운로드 링크) |
decrypt |
해독 | 비밀번호 확인 버튼으로 부적절 |
retention |
보유 | 보관 기간이 더 자연스러움 |
mailsSent |
이메일(들)은 서공적으로 보낼. | 오타, 비문 |
bucketSizeExceed |
업로드 크기 %% 은/는 최대의 %% 초과합니다 | 비문 |
fileSizeExceed |
파일 크기 %% 은/는 최대의 %% 초과합니다 | 비문 |
createNewUploadSession |
새로운 업로드 세션 창조하기? | 번역투 (항목 3에서 새 키로 대체) |
작업 방식: 위 표 외에도 ko.js 전체와, 항목 1~3 및 이후 항목에서 새로 추가된 키를 전수 점검한다.
메일 관련 키(mail*)는 실제로 쓰이는지 확인한 뒤, 안 쓰이면 정리 대상에서 제외한다.