스태프 서비스
이 페이지는 lumie-backend/modules/staff를 다룹니다. 문서 경로는
admin-svc.md로 남아 있지만, Gradle 모듈, Java package, internal API는
staff를 사용합니다.
책임
staff 모듈은 다음을 소유합니다.
- auth user에 연결된 tenant 범위 staff row
- permission catalog 조회
- staff별 permission 할당
- deactivate, reactivate, terminate, delete, reset password, login ID change 같은 staff lifecycle 작업
- tenant owner registration commit 후의 owner bootstrap
소스 경로
| Path | 역할 |
|---|---|
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/web | 공개 staff, permission, statistics controller |
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/event/OwnerRegisteredListener.java | owner bootstrap listener |
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/internal/StaffServiceAdapter.java | internal monolith API 구현 |
lumie-backend/modules/staff/src/main/java/com/lumie/staff/application/service | staff command, query, permission, dashboard service |
lumie-backend/modules/staff/src/main/java/com/lumie/staff/domain/entity | Staff, Permission, StaffPermission |
lumie-backend/libs/internal-api/src/main/java/com/lumie/staff/api/StaffService.java | class, lecture, content 등 다른 모듈이 쓰는 internal 계약 |
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sql | rename 이전 admin/permission table의 baseline |
lumie-backend/app/src/main/resources/db/migration/public/V26__rename_admin_tables_to_staff.sql | admins를 staff로, admin_permissions를 staff_permissions로 rename |
공개 인터페이스
| Surface | Entrypoints |
|---|---|
| Staff CRUD | POST /v1/staff, GET /v1/staff, GET /v1/staff/{id}, PATCH /v1/staff/{id}, DELETE /v1/staff/{id} |
| Credentials and lifecycle | 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 |
| Permissions | GET /v1/staff/{staffId}/permissions, PUT /v1/staff/{staffId}/permissions, GET /v1/permissions, GET /v1/permissions/categories |
| Dashboard | GET /v1/staff/statistics/dashboard |
내부 인터페이스
StaffService는 다음을 export합니다.
- staff ID 또는 auth user ID 기준 staff 조회
- tenant 내부 staff ID 검증
- staff permission 조회
- 전체 staff 목록
- owner bootstrap용
createOwnerStaff(...)
이 internal API는 teacher나 author를 해석하기 위해 class, lecture, content 모듈에서 많이 사용됩니다.
집계와 테이블
| Aggregate | Table | 참고 |
|---|---|---|
Staff | staff | tenant 범위 staff row를 auth user에 연결 |
Permission | permissions | permission catalog 항목 |
StaffPermission | staff_permissions | permission code를 staff ID에 할당하는 composite-key row |
여기서 중요한 구현 세부사항 두 가지:
Staff.role은 staff row에 직접 저장되지 않습니다. auth 모듈의userstable을 대상으로 한 Hibernate@Formula를 통해 읽습니다.StaffPermission은TenantScopedEntity를 상속하지 않으며,@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를 통해 생성할 수 있는 역할은
MANAGER와INSTRUCTOR뿐이며,OWNER는 시스템이 관리합니다. - staff는 자기 자신을 관리할 수 없고, 역할 계층은
Role.canManage(...)로 강제됩니다. - 삭제는 비활성 staff에 대해서만 영구적으로 수행되며, 모듈은 먼저 배정된 class나 예정된 상담 일정이 없는지 확인합니다.
- permission 쓰기는 기존 할당을 삭제한 뒤 새 집합을 넣는 방식으로 전체 교체를 수행합니다.
- 비밀번호 재설정과 login 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(...)를
호출하기 때문에, 빠진 permission code는 merge되거나 유지되지 않고 제거됩니다.
의존성과 경계
| Dependency | 존재 이유 |
|---|---|
AuthService | auth user 생성/삭제, 비밀번호 재설정, login ID 변경 |
BillingService | MetricType.ADMINS 기준 admin quota 검사 |
ClassService | 아직 class를 소유한 staff의 삭제 방지 |
ContentService | 예정된 상담 일정이 있는 staff의 삭제 방지 |
실패 모드
- 생성과 수정은
DUPLICATE_PHONE,INVALID_ROLE,INSUFFICIENT_PERMISSION,AUTH_OP_FAILED로 실패할 수 있습니다. - 삭제는 작업자가 자신을 대상으로 삼거나, 더 높은 역할을 관리하려 하거나, 활성 staff를 삭제하려 하거나, 배정된 class 또는 예정된 일정이 남아 있을 때 실패합니다.
- owner bootstrap 실패는 로그에 남지만, 이미 commit된 owner registration을 rollback하지는 않습니다.