Skip to content

feat: 임시멤버(명부) 일괄 생성 엔드포인트 POST /users/temporary#11

Open
youngunghan wants to merge 4 commits into
mainfrom
feat/temp-member-import
Open

feat: 임시멤버(명부) 일괄 생성 엔드포인트 POST /users/temporary#11
youngunghan wants to merge 4 commits into
mainfrom
feat/temp-member-import

Conversation

@youngunghan

@youngunghan youngunghan commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

DONE

신규 API — 회원 명부 파일 업로드 → 임시멤버 일괄 생성

POST /users/temporary (admin, multipart/form-data, field file)

  • .xlsx 또는 .csv 업로드 → 백엔드에서 파싱 → DB에 없는 사람을 임시회원으로 생성
  • 파일 판별: 확장자(.xlsx/.csv) 기준, 그 외 거부
  • 첫 행 헤더로 이름/학번 열 인식 (한·영, 열 순서 무관)
  • student_id 매칭: 기존(already_exists) / 파일 내 중복(duplicate_in_request) skip
  • 검증 메시지:
    · 잘못된 파일 → "파일 양식이 올바르지 않습니다."
    · 헤더 누락 → "이름 헤더를 찾을 수 없습니다." / "학번 헤더를 찾을 수 없습니다."
    · record 필드 누락 → "김와플"의 학번을 찾을 수 없습니다. (해당 행만 skip + 보고)

결정 (FE 확인 필요)

  • 파싱은 백엔드 / 파일 판별은 확장자(.xlsx/.csv) / record 누락은 skip+보고 / 상태컬럼은 기본값

@youngunghan youngunghan force-pushed the feat/temp-member-import branch from 1c01c44 to e62f061 Compare July 1, 2026 15:19
Admin bulk-creates temporary members from a JSON roster (name, student_id).
People not already in the DB become temp members, matched/deduped by
student_id (idempotent: already_exists / duplicate_in_request).

- users.email -> nullable, new is_temporary column + indexes (alembic a1b2c3d4e5f6)
- Enforce "temp member != real member": excluded from /users/pending, and
  blocked from /approve and from project membership (400 guards)
- Input normalization (strip whitespace / zero-width / BOM), roster max 2000, admin-only
- Tests: tests/test_temp_members.py (20 cases)
@youngunghan youngunghan force-pushed the feat/temp-member-import branch from e62f061 to 9126e3e Compare July 1, 2026 15:22
@swfs0417 swfs0417 self-requested a review July 2, 2026 05:19
Team decision: the frontend uploads the raw .xlsx and the backend parses it,
instead of the frontend parsing to JSON.

- POST /users/temporary now accepts an .xlsx UploadFile (multipart) instead of JSON
- app/services/roster.py: header-based column detection (name/student_id,
  Korean/English aliases, column-order agnostic); rows validated via TempMemberInput
- Blank/malformed rows are skipped as "invalid" (not a whole-file 422)
- New errors: INVALID_ROSTER_FILE (400), EMPTY_ROSTER (422), ROSTER_TOO_LARGE (400)
- Add openpyxl; tests rewritten to upload in-memory .xlsx
- Detect file type by extension (.xlsx / .csv); reject others as
  "파일 양식이 올바르지 않습니다."
- Per-column header errors ("이름/학번 헤더를 찾을 수 없습니다.")
- Per-record missing-field skip with reason + Korean message
  (missing_name / missing_student_id)
- SkippedMember gains a `message` field; route maps reason to message
- Drop the now-unused TempMemberInput schema (logic moved to roster.py)
Comment thread app/routes/users.py
db,
[(m.name, m.student_id) for m in request.members],
)
content = await file.read()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 row 크기 말고는 입력 크기 제한이 없는데, 용량 기준으로도 제한하는 게 좋을 것 같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants