34 Commits

Author SHA1 Message Date
b18a9af195 feat(helper): 입력창 textarea(줄바꿈) + 회사 생활도 답하도록 확장
All checks were successful
CI / backend-test (push) Successful in 2m9s
CI / frontend-build (push) Successful in 45s
CI / backend-dep-scan (push) Successful in 29s
- 입력이 <input> 한 줄뿐이라 줄바꿈이 안 됐다 → <textarea>로. Enter 전송,
  Shift+Enter 줄바꿈. 답변은 이미 pre-wrap이라 줄바꿈 반영됨.
- LlmAssistant 프롬프트에 회사 생활·직장 예절·업무 소통(보고·이메일·질문법·
  온보딩)을 신입 눈높이로 답하도록 추가. mirim 온보딩 교육과 결이 맞음.
2026-07-23 14:26:55 +09:00
8276870252 feat: AI 학습 도우미 위젯 + 피드백 초안 영역 확대
All checks were successful
CI / backend-test (push) Successful in 1m18s
CI / frontend-build (push) Successful in 58s
CI / backend-dep-scan (push) Successful in 28s
- 우측 하단 플로팅 AI 학습 도우미. 학생이 막힐 때 질문하면 로컬 LLM
  (qwen3:8b, 빠른 대화형)이 정답 대신 힌트·설명으로 답한다.
  LlmAssistant + POST /api/assistant/ask + HelperWidget.
- 멘토 피드백 textarea min-height 200px — AI 초안 여러 문장이 한눈에.
2026-07-23 12:55:50 +09:00
c26207ee48 feat: 멘토 코딩 피드백 AI 초안 기능
All checks were successful
CI / backend-test (push) Successful in 1m16s
CI / frontend-build (push) Successful in 51s
CI / backend-dep-scan (push) Successful in 28s
멘토가 코딩 제출물에 피드백을 쓸 때, 로컬 LLM(gpt-oss:20b)이 초안을
만들어 준다. 최종 판정은 멘토가 한다 — LLM은 초안 작성자일 뿐이다.

- LlmFeedbackDrafter: Ollama /api/chat 호출 (TelegramNotifier 패턴 —
  비활성 시 조용히, 예외 삼킴, 학습 포인트 주석)
- POST /api/mentor/coding-submissions/{id}/feedback-draft (초안만, 저장 안 함)
- 멘토 화면에 "AI 초안 받기" 버튼 → textarea 채움 → 멘토가 고쳐 저장
- llm.base-url 미설정 시 기능 자동 비활성 (모델 없어도 앱 정상 동작)

호출 규칙(/api/chat, num_ctx 16384, gpt-oss:20b)은 E:/llm 평가 근거:
qwen 계열은 async 버그를 "정상"이라 놓쳐 피드백에 위험, gpt-oss는 6/6.
2026-07-23 11:36:19 +09:00
1b559f5da2 fix: 전면 결함 스윕 — 4관점 감사 확정 결함 일괄 수정
All checks were successful
CI / backend-test (push) Successful in 1m10s
CI / frontend-build (push) Successful in 40s
CI / backend-dep-scan (push) Successful in 28s
4개 전문 감사관(프론트·백엔드·일관성·UX) 병렬 검증 결과 반영.

버그/깨짐(HIGH·MED):
- HtmlCssPage: 존재하지 않는 /learn/computer → /learn/hardware(깨진 링크, 대시보드로 튕김)
- MentorAnalyticsService: 체크리스트 활동(Progress.checkedAt)이 '마지막 활동'에 누락돼
  체크리스트만 한 학생이 '관심 필요'로 오분류되던 버그 수정(+회귀 테스트)
- 퀴즈 핫스팟 평균 정수절삭 → Math.round로 통일

접근성/UX(MED):
- CodeEditor: 코드 입력창에 aria-label 추가(WCAG 4.1.2, 스크린리더 이름 부여)
- 멘토 제출물 표: tr role=button → 셀 안 button으로(표 의미구조 보존 + 키보드)
- 대시보드 체크박스 토글 실패 시 무피드백 → try/catch + 에러 안내

콘텐츠/문서 드리프트:
- 초급 미니프로젝트 JS 예제 var→let(let/const 가르치며 var 쓰던 자기모순)
- README 강좌 수(71→83+48), BACKUP.md 복원검증 카운트(퀴즈655·문서19·코딩25)
- 브랜드/코스수 주석 정리(수습 플랫폼→학습 플랫폼, 72개→비수치화), 퀴즈 결과 '점'→'%'

테스트 백엔드 51→52, 프론트 30 유지. 운영 배포·스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:01:05 +09:00
bc2807db79 feat(mentor): 코호트(기수) 관리 + 기수 분석 대시보드
All checks were successful
CI / backend-test (push) Successful in 1m13s
CI / frontend-build (push) Successful in 40s
CI / backend-dep-scan (push) Successful in 29s
멘토 화면의 '분석·기수관리'를 개인 카드 나열에서 '묶어서 본 그림'으로 끌어올림.

기수(코호트):
- Flyway V2 마이그레이션으로 student_profile.cohort 추가(기존 학생 '2026' 백필)
- 가입 시 현재 기수(app.current-cohort, .env로 매년 변경) 자동 배정
- 명단에 기수 컬럼 + 배지 클릭으로 변경(POST /mentor/students/{id}/cohort)
- CSV 내보내기·StudentProfileResponse에 기수 포함

분석 대시보드(GET /mentor/analytics?cohort=):
- MentorAnalyticsService: 코호트별 KPI(진도·퀴즈·코딩·리뷰대기),
  학생별 마지막 활동 계산(4개 테이블 타임스탬프 max), 관심필요(7일+ 미활동),
  어려운 지점(평균 낮은 퀴즈·통과율 낮은 코딩 top5)
- MentorAnalytics 컴포넌트: 기수 선택·KPI 카드·관심필요·핫스팟, 슬러그→제목 해석

테스트: 백엔드 47→51(분석 4), 프론트 27→30(분석 3).
운영 검증: V2 빈DB→운영 무손실 적용(3명 백필), 분석 API·대시보드 브라우저 확인, 스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 13:29:49 +09:00
9045303a7a fix(content): 전문가 검증 지적 2건 반영 (티어 강좌 정밀성)
All checks were successful
CI / backend-test (push) Successful in 1m1s
CI / frontend-build (push) Successful in 38s
CI / backend-dep-scan (push) Successful in 29s
4개 트랙 전문 리뷰(강좌 48·퀴즈 240) 결과 CRITICAL 0·정답키 오류 0.
확정된 정밀성 MINOR 2건만 수정:

- Lv2 테스트 강좌: 'at>0→at>=0으로 바꾸면 빨간불' 실습이 제시된 테스트로는
  안 깨지던 문제(살아남는 뮤턴트) — 선두 @ 케이스 테스트 추가로 실제 성립하게
- Lv3 인덱스 강좌+퀴즈 해설: 전방일치 LIKE('보안%')가 Postgres 기본 로케일에선
  일반 B-Tree를 못 탄다는 조건(text_pattern_ops 필요) 명시 — 운영 DB 해설도 갱신

정답키·사실오류·버그코드는 240문항·48강좌에서 발견되지 않음.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 12:51:55 +09:00
c5f875d990 feat(learn): 수준별 학습 과정 4종 — 초급·중급·고급·특급 48강좌 + 퀴즈 240문항
All checks were successful
CI / backend-test (push) Successful in 1m1s
CI / frontend-build (push) Successful in 42s
CI / backend-dep-scan (push) Successful in 30s
기존 84강좌는 수습용으로 유지하고, 헤더 셀렉트 박스로 과정을 전환한다.

과정 구성 (각 12강좌, 배우는 순서대로):
- 초급 — 코딩 첫걸음: 프로그램의 개념→HTML→JS→변수·조건·반복·함수→에러읽기→첫 게임
- 중급 — 실전 웹 개발: 비동기·배열→API설계→Express CRUD→DB·트랜잭션→인증→React 실전→테스트→배포
- 고급 — 아키텍처·성능·보안: 레이어드·패턴→인덱스·격리→캐싱·최적화→OWASP·인증설계→관측·무중단배포
- 특급 — 대규모 시스템: 분산기초·CAP→메시지큐→MSA→샤딩→대용량 설계→k8s→SRE→시스템설계 3연작→ADR

구현:
- trackContext(localStorage 유지) + 헤더 TrackSelect + 테스트 5개
- levelCatalog(lv1-~lv4- 슬러그, 기존과 충돌 없음) + 라우트·퀴즈 자동 연결
- LearnHubPage 과정 인식(과정별 인사말·진도), CertificatePage 분모 버그 선제 수정
- 퀴즈 240문항: quizzes.json 415→655 + 운영 DB 삽입(655/131 확인)

생성 파이프라인: 설계 4 → 작성 48(병렬) → 검수 4 에이전트.
검수 BLOCKER 2건(백틱 충돌·OpenAPI 표기) 수정 후 빌드 통과.
운영 검증: 4개 과정 전환·강좌 렌더·퀴즈 로드·선택 유지 브라우저 확인, 스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 12:13:25 +09:00
db54b9bc19 feat(db): Flyway 마이그레이션 도입 (ddl-auto update → validate)
All checks were successful
CI / backend-test (push) Successful in 1m1s
CI / frontend-build (push) Successful in 23s
CI / backend-dep-scan (push) Successful in 29s
스키마를 '앱이 자동 변경'에서 '버전 관리되는 마이그레이션'으로 전환:
- flyway-core + flyway-database-postgresql 추가
- V1__baseline.sql: 현재 운영 스키마(테이블 13·제약 31·인덱스 1)를 그대로 박제
- ddl-auto: update → validate (Hibernate는 이제 검증만, 스키마는 Flyway 소유)
- baseline-on-migrate: 기존 운영 DB는 V1 미실행·baseline 표시, 신규 DB는 V1 실행

안전 절차로 진행:
- 도입 전 운영 DB 백업(pg_dump -Fc)
- 신규 DB 경로 검증(빈 DB→V1 실행→validate 통과→기동) 후 운영 배포
- 운영: 기존 스키마 baseline(무실행)→validate 통과→기동, 데이터 무손실(users=4·profile=3) 확인
- 테스트 잔재 pgcrypto 확장 제거

향후 스키마 변경은 V2, V3...로 의도를 남긴다. 엔티티만 바꾸고 마이그레이션을
안 쓰면 validate가 기동 시 실패해 알려 준다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 07:59:20 +09:00
0e3ab14b14 fix(security): Spring Boot 3.4.2 → 3.5.16 (OSV 발견 CVE 전부 해결)
All checks were successful
CI / backend-test (push) Successful in 1m2s
CI / frontend-build (push) Successful in 23s
CI / backend-dep-scan (push) Successful in 27s
OSV-Scanner가 찾은 누적 CVE를 관리 의존성 버전 상향으로 일괄 해결:
- tomcat-embed-core 10.1.34 → 10.1.55 (CVSS 9.8 포함 다수)
- spring-security-* 6.4.2 → 6.5.11 (CVSS 9.1 포함)
- spring-web/webmvc/core/context 6.2.2 → 6.2.19
- assertj-core 3.26.3 → 3.27.7
취약 의존성 전부 OSV 수정버전 이상으로 확인(dependency:tree 대조).

현재 GA 라인(3.5.x) 최신 패치라 지원 수명도 길다. 표준 스타터만 써서
마이너 상향에도 회귀 없음: 전체 47개 테스트 통과.
운영 검증: 세션 로그인 200·멘토 권한 200·감사쓰기(실 IP)·비로그인 401·스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 07:27:35 +09:00
e04e93cf18 feat(security): 보안 3종 — PII 감사로그·공개쓰기 레이트리밋·CI 의존성 스캔
All checks were successful
CI / backend-test (push) Successful in 1m22s
CI / frontend-build (push) Successful in 24s
CI / backend-dep-scan (push) Successful in 31s
SEC-02 PII 감사로그(누가·언제·어디서 개인정보를 조회/내보냈/파기했나):
- AuditLog 엔티티 + AuditLogRepository + AuditService(DB 영속 + slf4j 이중기록)
- MentorController: 명단조회·CSV내보내기·비번재설정·학생파기 4개 민감지점에 기록
- GET /api/mentor/audit-logs + MentorPage에 '개인정보 접근 기록' 표(남기기+보기 짝)
- 미성년 수습생 PII라 개인정보보호법상 접근기록 의무 대응

SEC-03 공개쓰기 레이트리밋(무인증 남용 방어):
- RateLimiterService(범용 고정창, Clock 주입, 메모리) — LoginAttemptService와 별도(단일책임)
- signup(30건/시간/IP: 대량가입·알림폭탄 차단), find-id(15건/10분/IP: 이름열거 차단)
- 초과 시 429. ClientIp 유틸로 rightmost XFF(Caddy 뒤 실제 IP) 공용화(AuthController 중복 제거)

CI 의존성 스캔:
- 프론트 npm audit(--omit=dev --audit-level=high, 비차단), 백엔드 OSV-Scanner v2.4.0(비차단)
- 둘 다 continue-on-error로 '보고 전용' 시작 → 무관한 신규 권고로 CI가 붉어지지 않게

적대적 보안 리뷰(4관점→검증) 확정 4건(전부 LOW) 반영:
- 파기 감사에 대상 아이디/이름 박제(파기 후 id는 dangling → '누구'를 못 알던 문제)
- 가입 한도 10→30(기수 동시 온보딩 대비), 감사 저장 전 로그 선기록(DB실패 시 흔적 유지)
- 고정창 경계 2배 특성 문서화, npm audit 비차단 전환

테스트 29→47(RateLimiter 5·Audit 3·SecurityContract 2 추가), 전부 통과.
운영 검증: audit_log 테이블 생성·감사쓰기(실IP 포함)·find-id 429·스모크 11/11 확인.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:52:44 +09:00
6ba8776e04 ci: 10점 향한 빠른 승리 3종 — 브랜치 보호·커버리지·린트/포맷
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / backend-test (push) Has been cancelled
1) 브랜치 보호(main): CI 두 개 초록불 + 승인 1 없이는 merge 불가,
   학생 직접 push 차단(PR 워크플로), 관리자만 운영·배포용 직접 push (Gitea 설정)
2) 커버리지 측정:
   - 백엔드 JaCoCo — 현재 라인 36.7%, 바닥 30% ratchet(테스트 지우며 후퇴하면 빌드 실패)
   - 프론트 vitest 커버리지 — 채점 로직 runSql.js 라인 40% 바닥
3) 린트/포맷 CI 강제:
   - 프론트 ESLint 9(flat) 신규 — 버그류 에러, 훅 오용 검출 (현재 위반 0)
   - 백엔드 Spotless — 안 쓰는 import·줄끝 공백·파일끝 개행 (기존 코드 이미 clean, 재정렬 없음)
   - ci.yml에 포맷검사·린트·커버리지 단계 추가

백엔드 37개 테스트 + 커버리지 게이트 통과, 프론트 ESLint·커버리지 통과 확인.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:02:08 +09:00
edafae9f86 test(SEC-01): 보안·권한 계약 통합테스트 8개 (@WebMvcTest)
All checks were successful
CI / backend-test (push) Successful in 50s
CI / frontend-build (push) Successful in 17s
SecurityConfig의 URL→권한 규칙을 실제로 검증 — 규칙이 깨지면 CI에서 빨간불:
- /api/mentor/**: 비로그인→401, STUDENT→403, MENTOR→200
- /api/**(코딩): 비로그인→401, 로그인→200
- /api/auth/login·signup·find-id: permitAll(잘못된 본문 400으로 도달 증명)
- 인증은 실제 앱과 동일하게 세션에 SecurityContext를 넣어 흉내(가짜 우회 아님)
- @WebMvcTest 슬라이스 + @MockitoBean — DB 없이 웹계층+보안필터만 로드
- spring-security-test 불필요(세션 직접 주입이 더 충실) → 의존성 추가 안 함

전체 37개 테스트 통과(백엔드 29→37). 권한 규칙이 이제 자동 회귀 방지된다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:33:37 +09:00
4d5c6d2e2b feat: 학습센터 '앱 개발' 카테고리 12강좌 추가 (안드로이드·iOS·크로스플랫폼)
다중 에이전트로 하우스 스타일(ReactIntroPage) 그대로 12개 강좌 페이지 생성:
앱의 지형 → 안드로이드 구조 → Kotlin → iOS 구조 → Swift → React Native →
Flutter → 앱 내비게이션 → 상태·데이터 → PWA → 앱 보안 → 스토어 배포

- 프레이밍: '웹 개발자가 보는 모바일' — 학생 React 지식을 다리로(RN·PWA),
  안드로이드·iOS 둘 다 정확히 다루되 인식·literacy 수준
- 각 페이지: hero·pill-nav·6~7섹션·SVG 다이어그램·tip/warn·다음코스 링크,
  색은 전부 테마 변수(하드코딩 0), 386~489줄
- courseCatalog에 lazy import 12 + '앱 개발' 카테고리(라우팅·허브·완료바 자동)
- 확인 퀴즈 60문항(코스당 5) — quizzes.json 병합 + 운영 DB 적재
- 총 코스 71→83

검증: 빌드 통과, 하우스스타일 일괄 점검(12/12 준수), 운영 E2E —
학습센터 앱개발 카테고리 노출·React Native 렌더·PWA 퀴즈 채점(정답 서버보관)·콘솔에러0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:36:28 +09:00
0a4c769d00 feat: 고도화 배치 7건 — 보안헤더·다운감시·로그회전·개인정보파기·문서정합성
[보안]
- Caddy 보안 응답헤더(HSTS·X-Frame-Options:DENY·X-Content-Type-Options·Referrer-Policy) +
  Server 헤더 제거. :80 catch-all 제거로 HTTP→HTTPS 자동 리다이렉트 복원(평문 표면 제거)
[운영 안정성]
- 헬스체크 스크립트 + systemd 타이머(2분): 공개 URL 두드려 UP/DOWN 변화 시에만 텔레그램 알림
- Docker 로그 회전(json-file 10m×3, YAML 앵커 공통적용) — 디스크 풀로 인한 조용한 다운 방지
[개인정보]
- 학생 파기: DELETE /api/mentor/students/{id} (멘토 권한) → 자식6테이블 먼저·계정 나중,
  @Transactional. 동의화면이 약속한 '파기' 이행. 멘토 화면에 이름입력 확인 파기 버튼
  StudentPurgeService + 테스트3(삭제순서·멘토거부·404)
[문서 정합성 — 지금 쓰는 학생 대상]
- STUDY.md: 없는 파일 가리키던 코드지도 정정(controller/→web/, ChecklistPage→DashboardPage,
  DocumentsPage→DocsPage, SubmissionsPage→AssignmentsPage)
- README: '6주'→8주, 학습센터/퀴즈/코딩 소개 추가, 폴더구조·초기계정(멘토만 시드·랜덤비번) 현행화
- 퀴즈 만점 안내: 없는 '코스 완료' 버튼 대신 '[← 코스로] 돌아가 완료' 로 정정

검증: 테스트29 통과, 운영에서 헤더4종·308리다이렉트·헬스체크·파기 E2E(무인증401·멘토200·
자식전체0건) 확인. 나머지 13건은 티켓 풀 확장감으로 남김.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:00 +09:00
bb12d072b1 docs: 코드리뷰 실습 가이드 + 캡스톤·데모데이 문서 추가
- code-review.html: 5~8주차 티켓 흐름에 상시 코드리뷰 도입.
  왜/리뷰 흐름(PR→동료+멘토→반영→merge)/리뷰어 체크리스트/
  좋은·나쁜 코멘트 예시/받는 자세. 우리 실제 버그를 예시로.
- capstone-demoday.html: 7~8주차 캡스톤 = 1인 1기능(플랫폼 위) +
  8주차 금 데모데이. 밤샘 해커톤 아닌 미니 스프린트+발표. 일정·발표법·평가 연결.
- 평가 루브릭 ③ 협업·소통에 코드리뷰 항목 명시(주는 것/받는 것) + dim-note 스타일
- DocsPage에 'PRACTICE(실습)' 카테고리 추가, documents.json + 운영 DB 시드(학생 노출)

검증: 학생 계정에 두 문서 노출·렌더 확인. 커리큘럼 확장 없이 기존 티켓·PR·루브릭에 얹음.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:56:38 +09:00
84144a1608 fix(security+a11y): 종합 감사(PMD+5관점, 29에이전트) 확정 12건 수정
[HIGH]
- 로그인 잠금 XFF 스푸핑 우회: X-Forwarded-For leftmost(위조 가능) → rightmost(Caddy가 붙인 실제 IP) 사용
- 멘토 기본 비밀번호 하드코딩 제거: env 미설정 시 SecureRandom 생성 후 로그 1회 출력.
  compose에 MENTOR_INITIAL_PASSWORD 추가
- 클릭 카드 키보드 접근 불가(학습문서 전체): Card에 role=button·tabIndex·Enter/Space·aria-label

[MEDIUM]
- CSV 수식 인젝션: =+-@ 로 시작하는 학생 자유입력 앞에 ' 붙여 엑셀 수식화 차단 (+회귀 테스트)
- 학습문서 17개 <html lang=ko> 골격 주입 (스크린리더 한국어 발음)
- 과제 제출 폼 label htmlFor/id 연결
- 상단 아이콘 링크(👤·🔑) aria-label + 이모지 aria-hidden
- 멘토 제출물 표 행 키보드 접근(role/tabIndex/Enter·Space)
- 배지 amber/teal 글자 대비 4.5:1 확보(테마별 --amber-text/--teal-text)

[LOW]
- 코드에디터 포커스 표시(:focus-within), 문제번호 색 대비(--muted), 카드 :focus-visible
- 폼 오류 메시지 role=alert (스크린리더 즉시 안내)

검증: 테스트 26개 통과, 운영에서 XFF 우회 차단(6회째 429)·카드 role/aria·문서 lang=ko 확인.
PMD 29건은 전부 저심각 스멜로 실질 결함 없음. 각 수정에 원리 학습 주석.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:38:30 +09:00
f557d7a189 feat: 학생용 '내 정보' 확인·수정 페이지 (/profile)
- GET/PUT /api/profile/me — 경로에 id 없이 세션에서 본인 식별 (IDOR 원천 차단, 주석화)
- 수정 가능: 이름·회사이메일·생년월일·연락처·주소·학교·전공·비상연락처
  불가: 아이디(불변)·트랙(멘토 소관)·동의시각(증빙은 본인도 수정 불가)
- 이메일 변경 시에만 중복 검사(내 값 유지는 통과), 검증 규칙은 가입과 동일
- 수정할 엔티티는 트랜잭션 안 재조회(managed) — 비밀번호 변경 버그의 교훈 적용
- DTO 변환을 트랜잭션 안에서 — open-in-view:false의 LAZY 함정 회피 (주석화)
- 수정 시 멘토 텔레그램 알림 (경리 CSV 재발급 필요 알림)
- 네비 👤 아이콘 (학생만 표시), Field 컴포넌트는 밖에 정의(키보드 버그 교훈 참조)
- 테스트 3개 추가 → 전체 25개 통과

운영 E2E 검증: 조회→이메일·주소 수정→재조회 반영 확인.
배경: 가입한 학생 2명의 주소가 불완전하고 이메일 오타 의심 — 본인이 직접 교정 가능하게.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 11:52:04 +09:00
0336213ca2 fix: 기본기 전수 감사(38 에이전트, 172파일)에서 확정된 버그 10건 수정
[major]
- client.js 401 인터셉터: /auth/** 전체를 예외로 — 비밀번호 변경에서 현재 비번
  틀리면 세션 만료로 오인해 /login으로 튕기던 버그 (에러 안내 도달 불가였음)
- Submission (assignment_id,user_id) 유니크 제약 추가(엔티티+운영DB) —
  동시 제출 시 중복 행 → 이후 해당 과제 재제출 영구 500 방지. 자매 엔티티와 일관화

[minor]
- AuthContext: 마운트 시 /auth/me의 늦은 401이 로그인 성공을 덮는 경쟁 → 함수형 업데이트로 보존
- AssignmentsPage: key에 제출 id 포함 → 첫 제출 직후 카드 리마운트로 성공 메시지 소실
  → key를 과제 id로 고정, 메시지는 접히는 영역 밖으로
- CodeEditor: 텍스트 선택 후 Tab 시 커서가 선택 길이만큼 밀림 (selectionEnd+2 → selectionStart+2)
- div.olist>ol 마크업 오류로 목록 번호 이중 표시 — 7개 강좌 10곳 전부 ol.olist로 통일
  (감사가 3파일 발견, 전역 grep으로 4파일 추가 확보)
- SignupPage 검증을 서버 규칙과 일치: 아이디 4자+영숫자, 생년월일 과거만
- LoginRequest @NotBlank + @Valid: username 누락 시 NPE 500 → 400
- server.error.include-message: always — 서버의 한국어 에러 사유가 화면까지 전달
  ('이미 사용 중인 아이디입니다' 등, 그동안 폴백 문구만 보였음)

검증: 테스트 22개 통과, 운영에서 400/409 message/비번변경 에러표시 확인.
각 버그의 원리를 소스 주석에 학습 포인트로 기록.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 11:42:05 +09:00
765d30c479 fix(ui): 반응형 전수 조사에서 발견된 문제 4건 수정
전수 조사 범위: 앱 화면 14개 + 강좌 71개 + 학습 문서 17개 × 모바일(375)/태블릿(768)/데스크톱(1280)
— 가로 넘침 자동 검출(0건) + 주요 화면 육안 검증

수정:
- 문서 뷰어 '← 뒤로가기' 버튼 모바일에서 두 줄 꺾임 → nowrap
- 멘토 명단·퀴즈 표: '트랙'→'트/랙' 세로 꺾임 → .table--nowrap 유틸 신설
  (전역 td nowrap은 overflow:hidden 카드의 긴 셀을 잘라먹어 선택 적용)
- 가입 동의 체크박스: 두 줄 라벨에서 정렬 어긋남 → flex-start
- SQL 문제 설명 '왼쪽 아래' → '문제 아래' (실제 배치와 불일치, 시드+운영 DB 동시 수정)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:44:50 +09:00
a2eb5e587a feat(security): 로그인 무차별 대입 방어 — 5회 실패 시 5분 잠금
- LoginAttemptService: (아이디+IP) 단위 인메모리 실패 카운트
  · 아이디 단독 키 → 잠금 악용 DoS, IP 단독 키 → 공용 IP 연대 잠금. 둘을 묶어 해결
  · Clock 주입으로 '5분 후 해제'를 가짜 시계로 테스트 (ClockConfig 추가)
  · 5회 도달 시 WARN 로그(보안 사건 추적), 맵 청소로 메모리 누수 방지
- AuthController: 검증 '전에' 잠금 확인(잠금 중엔 정답 확인 기회도 안 줌) → 429
  · X-Forwarded-For에서 클라이언트 IP 추출 (Caddy 뒤 전용 — 신뢰 조건 주석화)
- LoginPage: 429 시 '5분 뒤 다시 시도' 안내
- 테스트 5개 추가 → 전체 22개 통과

운영 검증: 5회 실패 → 401, 6회째 → 429 확인. 다른 IP·계정은 영향 없음.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:17:37 +09:00
a52999d81d feat: 텔레그램 알림 봇 연동 — 학생 가입·과제·코딩 제출을 멘토에게 즉시 알림
- TelegramNotifier: 봇 API sendMessage 호출. 두 가지 원칙 —
  · 알림 실패가 본 기능(가입·제출)을 절대 막지 않는다: 예외 전부 삼킴 + @Async 비동기
  · 시크릿은 env로만: TELEGRAM_BOT_TOKEN/CHAT_ID 비어 있으면 기능이 조용히 꺼짐(로컬 무설정 OK)
  · 에러 로그에 예외 종류만 남김 — HTTP 에러 메시지의 URL에 토큰이 포함되는 유출 경로 차단
- AsyncConfig: @EnableAsync (없으면 @Async가 조용히 무시되는 함정 문서화)
- 연결 지점 3곳: AuthService.signup(가입) / CodingService.submit(코딩) / SubmissionService(과제)
- 테스트: 생성자에 mock 주입 + 가입 시 알림 발송 검증 1건 추가 → 전체 17개 통과

운영 검증: 봇(@awesomedevmirim_bot)→'2026 현장실습 사전 과제' 그룹 발송 확인,
실가입 E2E로 알림 발송 확인. 토큰은 서버 .env에만 존재(git 미포함).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:09:52 +09:00
a5d4437b27 feat: SQL 코딩 문제 추가 — 브라우저 SQLite(sql.js)로 실행·자동채점
- 문제 5개 시드: SELECT* → WHERE → ORDER BY+LIMIT → GROUP BY → JOIN
  (직원·부서 연습 데이터, SQL 입문 강좌 진도 순서와 일치)
- 실행 방식: sql.js(SQLite WASM)로 학생 브라우저 안 1회용 메모리 DB에서 실행
  → 서버 DB 무접촉. DROP/DELETE도 자유롭게 실험 가능 (runJs.js와 같은 격리 철학)
- 채점: 값만 비교(컬럼 별칭 허용), ORDER BY 문제만 행 순서 채점(orderMatters)
- 화면: 데이터 살펴보기(테이블+데이터), 실행 결과 표 vs 기대 결과 표 나란히 비교
- CodingProblem: setupSql 컬럼, isAutoGraded에 SQL 포함(JAVA만 수동)
- CodingServiceTest 3개 추가: SQL 채점값 저장·PASSED, JAVA 채점값 폐기(조작 방지), 빈 코드 400
  → 전체 17개 테스트 통과

운영 검증(브라우저 E2E): 정답 실행 '통과', 오답 시 기대 결과 비교 표시,
제출 시 서버에 PASSED 1/1 저장 확인. 문제 수 20→25 재시드.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:47:54 +09:00
cb0fb54185 feat: 회원가입 개인정보 수집·이용 동의 추가 (고지 후 동의 + 시각 증빙)
- 가입 화면: 수집 항목·목적·보유기간·거부권 안내문 + 필수 동의 체크박스
- SignupRequest: privacyConsent @NotNull @AssertTrue (서버가 최종 방어선)
- AuthService: 서비스 계층 이중 확인(defense in depth), 동의 시각은 서버 시계로 기록
- StudentProfile: privacy_consent_at 컬럼 추가 — boolean이 아닌 timestamp로 증빙
- 경리 CSV: '개인정보동의일' 열 추가 (감사 대비)
- 테스트 2개 추가: 동의 없으면(false/null) 400, 정상 가입 시 동의 시각 기록
  → 전체 14개 테스트 통과

운영 검증: 동의 false/누락 → 400, 동의 → 201 + DB privacy_consent_at 기록 확인

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:30:22 +09:00
8332f306f7 test: 인증·퀴즈 서비스 단위 테스트 추가 (오늘 난 버그 회귀 방지)
- AuthServiceTest 6개:
  · 비밀번호 변경이 실제로 저장되는지 (detached 엔티티 버그 회귀 테스트)
  · 현재 비번 틀리면 401·변경 안 됨
  · 회원가입 아이디/이메일 중복 409
  · 멘토 재설정은 학생 계정만(멘토 대상이면 403)
- QuizServiceTest 3개:
  · 안 푼 문제는 오답 처리·점수 집계
  · 답 null이어도 안 터짐
  · 퀴즈 없는 코스는 404
- PasswordEncoder는 진짜 BCrypt, 리포지토리만 mock (DB 없이 빠르게)
- User/QuizQuestion의 id는 ReflectionTestUtils로 주입(테스트 기법 예시)

전체 12개 테스트 통과 (기존 3 + 신규 9)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:18:37 +09:00
5e8f96719b fix: 비밀번호 변경이 실제로 저장되지 않던 버그 (detached 엔티티)
- change-password가 200을 반환하면서도 DB가 안 바뀌었음
- 원인: requireUser()가 트랜잭션 밖에서 조회한 detached User의 필드를 바꿔
  JPA 더티 체킹이 작동하지 않음 (managed 엔티티에만 작동)
- 해결: changePassword 트랜잭션 안에서 findById로 재조회 → managed 상태로 UPDATE
- 학습 포인트 주석 강화: '더티 체킹이 자동 저장'의 숨은 전제(트랜잭션이 관리하는 엔티티)

검증: 변경 200 → 새 비번 로그인 200 → 옛 비번 401. 틀린 현재비번 401.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:13:30 +09:00
ecb1298d93 feat: 학생 자율 가입 + 인적사항 수집 + 경리용 명단/CSV
- 테스트 계정(student1~4) 제거, 시드는 mentor1만 (MENTOR_INITIAL_PASSWORD 환경변수 지원)
- 학생은 회원가입으로 등록 — 첫날 첫 실습 겸 본인이 정보 입력
- StudentProfile 엔티티(users와 1:1 분리): 회사이메일(로컬만 저장+도메인 고정)·생년월일·
  연락처·주소·학교·전공·비상연락처. 주민번호·계좌 등 민감정보는 제외(종이 인사 절차)
- 회원가입 폼 확장 + 이메일 실시간 미리보기(hong → hong@awesomedev.dev)
- 멘토 페이지: 수습생 명단 표 + CSV 다운로드(BOM·이스케이프로 엑셀 한글 안전) — 경리 부담↓
- 이메일 아이디 중복·형식 검증(서버+프론트)

버그 수정: 명단 조회 LazyInitializationException(open-in-view:false) → JOIN FETCH로 해결
  (보안 리뷰가 경고했던 그 함정 — 주석으로 학습 포인트화)

검증: 가입 201 / 이메일중복 409 / 형식오류 400 / 명단 200 / CSV(헤더·BOM·이스케이프) / 학생→명단 403

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:00:03 +09:00
c5c4a5b1f9 fix(security): 무인증 계정 탈취 경로 차단 + 세션 고정 방어 + 거짓 주석 수정
보안 검토(4개 영역 병렬)에서 확인된 실제 취약점 수정:

[HIGH] 무인증 계정 탈취 연쇄 — find-id로 아이디 획득 → 아이디+이름만으로 비밀번호 재설정
  → 요청 2번으로 멘토 권한 탈취 가능했음 (공개 서버, 시드 이름 예측 가능)
  - 무인증 POST /api/auth/reset-password 제거
  - find-id 응답 아이디 마스킹 (mentor1 → me****1)
  - 대체 경로 신설: POST /api/auth/change-password(본인, 현재 비번 재인증),
    POST /api/mentor/users/{id}/reset-password(멘토 전용, 학생 계정만)
  - 프론트: 재설정 폼 → 안내로 교체, 비밀번호 변경 페이지·멘토 재설정 UI 추가

[MEDIUM] 세션 고정 — 로그인 시 세션 ID 미재발급 (직접 구현이라 formLogin의 방어가 빠져 있었음)
  - changeSessionId() 추가. 세션 부재 시 IllegalStateException 나는 것 확인해 가드 포함

[교재 품질] 코드와 어긋난 주석 수정 — 학생이 주석 신뢰를 잃는 것이 가장 큰 손해
  - Badge: 라벨표 이중화로 REVIEWED가 화면마다 다르게 표시되던 실제 버그 수정(PASSED 추가)
  - QuizResult: 정렬 없이 '가장 최근'을 가정하던 주석 → OrderBy 명시로 사실화
  - runJs: (0, eval) 설명 → 실제 코드(new Function)에 맞게 수정
  - runJs: Worker는 fetch가 되므로 '멘토 화면에서 학생 코드 실행 금지' 회귀 경고 명시

운영 검증: 로그인 200 / 무인증 재설정 401 / 학생→멘토API 403 / 멘토 재설정 200

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 07:58:50 +09:00
8486c7e656 fix(security): 세션 쿠키 보안 플래그 + 운영 로그 레벨
- 세션 쿠키에 HttpOnly(XSS로 탈취 방어)·Secure(HTTPS 전용)·SameSite=Strict(CSRF 방어) 적용
- Secure는 COOKIE_SECURE 환경변수로 분리 (로컬 http 개발에서도 로그인되도록 기본 false)
- 운영 로그 레벨을 LOG_LEVEL 환경변수로 분리, 운영은 INFO
- 학습 포인트 주석: 세션 인증의 3대 쿠키 플래그가 왜 필요한가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 07:19:21 +09:00
5a82ecb291 feat: 코딩 문제 20문항 — 브라우저 자동채점 + 멘토 코드 리뷰
읽고 아는 것과 짤 수 있는 것을 나눠 확인하는 세 번째 축(과제·퀴즈에 이어).

- 문제 20개: JS 12(자동채점) + Java 8(멘토 리뷰), 난이도 EASY 8/NORMAL 8/HARD 4
- JS 채점은 Web Worker에서 실행 — 서버 샌드박스 없이 무한루프(2초 타임아웃)·
  DOM 접근을 막는다. 테스트 48건은 모범답안으로 전수 검증함
- Java는 실행하지 않음(격리 비용·보안). 코드를 멘토가 읽고 피드백.
  서버가 자바 제출의 클라이언트 채점값을 버리므로 위조 불가
- 멘토 화면: 코드 원문·테스트별 통과/실패·학생별 진도·피드백 작성
- (문제, 학생)당 1행 유지 + attempts 증가. 에디터는 무의존성(textarea 기반)

검증: 백엔드 컴파일·프론트 빌드·시드 20건 적재·API 전 흐름(제출/재제출/
권한 403/빈코드 400)·브라우저 실사용(오답 3/4 → 정답 4/4 → 멘토 조회)
2026-07-17 00:27:52 +09:00
cd9c34d0a7 feat: 코스별 확인 퀴즈 — 71개 코스 355문항, 서버 채점
- 문항/응시기록 엔티티 + 시드(quizzes.json, 코스별 원본 quiz/*.json)
- API: 문항 조회(정답 제외)·제출/채점·내 결과 요약·멘토 학생별 성적
- 퀴즈 페이지: 라디오 선택→서버 채점→정답/해설 색 표시→다시 풀기
- 코스 하단 바에 '퀴즈 풀기' 추가, 멘토 페이지에 퀴즈 성적 표(평균·만점 수)
- 학습 포인트: 정답은 서버에만(브라우저에 비밀 두지 않기), 원본 기록 보존 설계

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:12:43 +09:00
6df3f308cc feat: 멘토 주차별 체크리스트 진도 뷰 + 적대적 리뷰 반영
- GET /api/mentor/checklist-progress: 주차별 총계(분모) + 학생별 완료 수(분자)
- 멘토 페이지에 학생×주차 진도 표 (완료율 3단계 색, 첫 열 sticky)
- 리뷰 반영: Promise.allSettled로 섹션 독립 로딩(+에러 배너),
  table-static 클래스로 비클릭 표의 hover 하이라이트 제거, 시드 부재 빈 상태 구분

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:53:14 +09:00
c4c3bb0712 feat: 멘토 페이지 학생별 코스 진도 현황 추가
- GET /api/mentor/course-progress: 학생별 완료 slug 목록 (MENTOR 전용, N+1 트레이드오프 주석)
- 멘토 페이지 상단에 학생 카드: 진도율 바 + 코스 완료 수 + 카테고리별 집계 칩

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:34:47 +09:00
b8c9da2d4a feat: 학습 센터 코스 진도 체크 — 완료 토글 API + 하단 완료 바 + 허브 진도율
- 백엔드: CourseProgress 엔티티(user+slug 유니크)·서비스·API
  (GET /api/course-progress/me, POST /api/course-progress/{slug}/toggle, slug 형식 검증)
- 프론트: CourseShell 래퍼로 72개 코스에 하단 완료 바 자동 부착,
  허브에 전체/카테고리별 진도율·완료 카드 표시(✓·청록 테두리)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:18:10 +09:00
111590104e feat: 수습 학습 플랫폼 최초 구축 (React + Spring Boot + PostgreSQL)
- 로그인/회원가입/아이디찾기/비밀번호재설정 (세션 기반)
- 대시보드(주차별 진도 체크리스트), 학습 문서 뷰어(17종), 과제 제출/멘토 피드백
- 코딩 기초·개발 환경 설치 가이드 학습 페이지
- 전 소스 한국어 학습 주석 — 수습생 교육용 저장소
- Docker 배포 구성 (Caddy + Spring Boot + PG + Gitea)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:18:12 +09:00