282 lines
28 KiB
Markdown
282 lines
28 KiB
Markdown
# 설계 기록
|
||
|
||
> **이 문서는 구현 전에 정리한 설계 기록입니다. 모두 구현되었습니다.** 현재 동작과 설정은
|
||
> [README](../README.md)와 [configuration.md](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`, 응답 CSP `sandbox` 적용 → 업로드된 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](Dockerfile)의 멀티스테이지 빌드(`frontend` 단계)는 구조 변경이 없음. 이미지 빌드 중에 프론트가 빌드되므로 도커 친화적
|
||
- 서버가 `lang.json`, `config.json`, `{sid}.json`으로 주는 계약
|
||
|
||
디자인 방향 (제안):
|
||
- 카드 기반의 깔끔한 레이아웃, 둥근 모서리, 넉넉한 여백, 큰 드롭존
|
||
- 노란색은 로고에만. UI 강조색은 중립적인 색(예: 슬레이트/인디고 계열 하나)
|
||
- 라이트/다크 모드 (`prefers-color-scheme`), 모바일 우선 반응형
|
||
- 한글 가독성을 위한 시스템 폰트 스택 (`Pretendard` 등 웹폰트는 오프라인 여부 확인 후 결정)
|
||
- 접근성: 키보드 조작, 포커스 표시, 충분한 대비
|
||
- Admin 페이지도 같은 스택과 스타일로 통일
|
||
|
||
진행 순서:
|
||
1. 화면 흐름(업로드/다운로드 순서) 확정 — 다음 항목들에서 논의
|
||
2. 흐름이 정해지면 업로드 화면 목업을 HTML로 보여주고 디자인 확인
|
||
3. 확정 후 구현
|
||
|
||
결정 (사용자 답변 반영):
|
||
- 서버는 외부 인터넷 제한이 없지만 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?`
|
||
- 기존 `createNewUploadSession` 키는 삭제하지 않고 둠
|
||
|
||
---
|
||
|
||
## 최종 단계: 번역 정리 (잊지 말 것)
|
||
|
||
다른 항목이 모두 확정된 뒤, **최종 화면과 문구에 맞춰** `lang/ko.js`(필요 시 `en.js`)의 오타와 의미가 불분명한 번역을 일괄 정리한다.
|
||
화면이 바뀌면 문구도 바뀌므로 마지막에 한 번에 한다.
|
||
|
||
알려진 문제 (현재 시점):
|
||
|
||
| 키 | 현재 | 문제 |
|
||
|---|---|---|
|
||
| `login` | 로드인 | 오타 (로그인) |
|
||
| `generateRandomPassword` | 렌덤 비밀번호를 생성하기 | 오타, 어색한 어미 |
|
||
| `downloadLink` | 링크 다운로드하기 | 뜻이 뒤집힘 (다운로드 링크) |
|
||
| `decrypt` | 해독 | 비밀번호 확인 버튼으로 부적절 |
|
||
| `retention` | 보유 | 보관 기간이 더 자연스러움 |
|
||
| `mailsSent` | 이메일(들)은 서공적으로 보낼. | 오타, 비문 |
|
||
| `bucketSizeExceed` | 업로드 크기 %% 은/는 최대의 %% 초과합니다 | 비문 |
|
||
| `fileSizeExceed` | 파일 크기 %% 은/는 최대의 %% 초과합니다 | 비문 |
|
||
| `createNewUploadSession` | 새로운 업로드 세션 창조하기? | 번역투 (항목 3에서 새 키로 대체) |
|
||
|
||
작업 방식: 위 표 외에도 `ko.js` 전체와, 항목 1~3 및 이후 항목에서 새로 추가된 키를 전수 점검한다.
|
||
메일 관련 키(`mail*`)는 실제로 쓰이는지 확인한 뒤, 안 쓰이면 정리 대상에서 제외한다.
|