본문으로 건너뛰기

스태프 서비스

이 페이지는 lumie-backend/modules/staff를 다룹니다. 문서 경로는 admin-svc.md로 남아 있지만, Gradle 모듈, Java 패키지, 내부 API는 staff를 사용합니다.

책임

staff 모듈은 다음을 소유합니다.

  • auth 사용자에 연결된 테넌트 범위 staff 행
  • 권한 카탈로그 조회
  • staff별 권한 할당
  • 비활성화, 재활성화, 종료 처리, 삭제, 비밀번호 재설정, 로그인 ID 변경 같은 staff 수명주기 작업
  • 테넌트 OWNER 등록 커밋 후의 OWNER 부트스트랩

소스 경로

경로역할
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/web공개 staff, 권한, 통계 컨트롤러
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/event/OwnerRegisteredListener.javaOWNER 부트스트랩 리스너
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/internal/StaffServiceAdapter.java모놀리스 내부 API 구현
lumie-backend/modules/staff/src/main/java/com/lumie/staff/application/servicestaff 명령, 조회, 권한, 대시보드 서비스
lumie-backend/modules/staff/src/main/java/com/lumie/staff/domain/entityStaff, Permission, StaffPermission
lumie-backend/libs/internal-api/src/main/java/com/lumie/staff/api/StaffService.java수업, 강의, 콘텐츠 등 다른 모듈이 쓰는 내부 계약
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sql이름 변경 이전 admin/permission 테이블의 기준선
lumie-backend/app/src/main/resources/db/migration/public/V26__rename_admin_tables_to_staff.sqladminsstaff로, admin_permissionsstaff_permissions로 이름 변경

공개 인터페이스

인터페이스진입점
Staff CRUDPOST /v1/staff, GET /v1/staff, GET /v1/staff/{id}, PATCH /v1/staff/{id}, DELETE /v1/staff/{id}
자격 증명과 수명주기POST /v1/staff/{id}/reset-password, PATCH /v1/staff/{id}/login-id, POST /v1/staff/{id}/deactivate, POST /v1/staff/{id}/reactivate, POST /v1/staff/{id}/terminate
권한GET /v1/staff/{staffId}/permissions, PUT /v1/staff/{staffId}/permissions, GET /v1/permissions, GET /v1/permissions/categories
대시보드GET /v1/staff/statistics/dashboard

내부 인터페이스

StaffService는 다음을 내보냅니다.

  • staff ID 또는 auth 사용자 ID 기준 staff 조회
  • 테넌트 내부 staff ID 검증
  • staff 권한 조회
  • 전체 staff 목록
  • OWNER 부트스트랩용 createOwnerStaff(...)

이 내부 API는 담당 교사나 작성자를 해석하기 위해 수업, 강의, 콘텐츠 모듈에서 많이 사용됩니다.

집계와 테이블

집계테이블참고
Staffstaff테넌트 범위 staff 행을 auth 사용자에 연결
Permissionpermissions권한 카탈로그 항목
StaffPermissionstaff_permissions권한 코드를 staff ID에 할당하는 복합 키 행

여기서 중요한 구현 세부사항 두 가지:

  • Staff.role은 staff 행에 직접 저장되지 않습니다. auth 모듈의 users 테이블을 대상으로 한 Hibernate @Formula를 통해 읽습니다.
  • StaffPermissionTenantScopedEntity를 상속하지 않으며, @PrePersist에서 TenantContextHolder.getRequiredTenantId()tenant_id를 씁니다.

런타임 흐름

등록 후 Owner 부트스트랩

Owner 부트스트랩이 멱등적인 이유

return staffRepository.findByUserId(userId)
.map(existing -> {
log.info("OWNER staff already exists for userId={}, tenant={} — idempotent skip",
userId, tenantSlug);
return toStaffDataFromEntity(existing);
})

이 로직은 재시작 후 중복 전달이나 publication 일부 재생이 일어나도 listener를 보호합니다.

핵심 동작

  • 공개 staff API를 통해 생성할 수 있는 역할은 MANAGERINSTRUCTOR뿐이며, OWNER는 시스템이 관리합니다.
  • staff phone은 사람 연락처이며 생성과 수정에서 숫자만 남긴 값이 11자리여야 합니다. 학원 전화번호, SMS 발신 번호, 알림 설정 전화번호는 이 staff lifecycle 규칙의 대상이 아닙니다.
  • staff는 자기 자신을 관리할 수 없고, 역할 계층은 Role.canManage(...)로 강제됩니다.
  • 삭제는 비활성 staff에 대해서만 영구적으로 수행되며, 모듈은 먼저 배정된 class가 없는지 확인합니다.
  • permission 쓰기는 기존 할당을 삭제한 뒤 새 집합을 넣는 방식으로 전체 교체를 수행합니다.
  • 비밀번호 재설정과 로그인 ID 변경은 로컬에서 자격 증명을 수정하지 않고 AuthService에 위임합니다.

대표 계약 예시

이 예시는 SetPermissionsRequest, StaffController, StaffCommandService.setStaffPermissions(...), StaffPermissionResponse와 일치합니다.

PUT /v1/staff/42/permissions

{
"permissions": {
"STUDENT_WRITE": "WRITE",
"CLASS_READ": "READ"
}
}

200 OK는 빈 body를 반환합니다. 이어서 GET /v1/staff/42/permissions를 호출하면 교체된 집합이 반환됩니다.

[
{
"permissionCode": "STUDENT_WRITE",
"accessLevel": "WRITE"
},
{
"permissionCode": "CLASS_READ",
"accessLevel": "READ"
}
]

setStaffPermissions(...)saveAll(...) 전에 deleteByStaffId(...)를 호출하기 때문에, 빠진 권한 코드는 병합되거나 유지되지 않고 제거됩니다.

의존성과 경계

의존성존재 이유
AuthServiceauth 사용자 생성/삭제, 비밀번호 재설정, 로그인 ID 변경
BillingServiceMetricType.ADMINS 기준 admin quota 검사
ClassService아직 수업을 소유한 staff의 삭제 방지

실패 모드

  • 생성과 수정은 staff phone이 숫자만 남겼을 때 11자리가 아니면 INVALID_PHONE_NUMBER로, 그 외 DUPLICATE_PHONE, INVALID_ROLE, INSUFFICIENT_PERMISSION, AUTH_OP_FAILED로 실패할 수 있습니다.
  • 삭제는 요청자가 자신을 대상으로 삼거나, 더 높은 역할을 관리하려 하거나, 활성 staff를 삭제하려 하거나, 배정된 class가 남아 있을 때 실패합니다.
  • OWNER 부트스트랩 실패는 로그에 남지만, 이미 커밋된 OWNER 등록을 롤백하지는 않습니다.

관측성과 할당량 동작

  • 일반 수명주기 및 permission 쓰기는 StaffCommandService를 통해 로그를 남깁니다.
  • OWNER 부트스트랩은 OwnerRegisteredListener에서 성공과 실패를 로그합니다.
  • 이 모듈은 quota를 인식하지만, 현재 billing internal adapter는 무제한 placeholder와 함께 allowed=true를 반환합니다. 즉, staff 모듈은 quota check를 호출하지만 사용량 enforcement가 다시 들어오기 전까지 monolith 측 billing 계약은 완화된 상태입니다.

검증

./gradlew :modules:staff:test
./gradlew :app:test --tests '*Staff*'
cd lumie-document/docusaurus && npm run build

예상 성공 신호:

  • Gradle이 BUILD SUCCESSFUL로 끝납니다.
  • StaffCommandServiceTest가 실패 없이 통과합니다.
  • Docusaurus가 backend/admin-svc에 대해 MDX 또는 broken-link 오류 없이 완료됩니다.

관련 페이지