본문으로 건너뛰기

스태프 서비스

이 페이지는 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.javaowner bootstrap listener
lumie-backend/modules/staff/src/main/java/com/lumie/staff/adapter/in/internal/StaffServiceAdapter.javainternal monolith API 구현
lumie-backend/modules/staff/src/main/java/com/lumie/staff/application/servicestaff command, query, permission, dashboard service
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.javaclass, lecture, content 등 다른 모듈이 쓰는 internal 계약
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sqlrename 이전 admin/permission table의 baseline
lumie-backend/app/src/main/resources/db/migration/public/V26__rename_admin_tables_to_staff.sqladminsstaff로, admin_permissionsstaff_permissions로 rename

공개 인터페이스

SurfaceEntrypoints
Staff CRUDPOST /v1/staff, GET /v1/staff, GET /v1/staff/{id}, PATCH /v1/staff/{id}, DELETE /v1/staff/{id}
Credentials and lifecyclePOST /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
PermissionsGET /v1/staff/{staffId}/permissions, PUT /v1/staff/{staffId}/permissions, GET /v1/permissions, GET /v1/permissions/categories
DashboardGET /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 모듈에서 많이 사용됩니다.

집계와 테이블

AggregateTable참고
Staffstafftenant 범위 staff row를 auth user에 연결
Permissionpermissionspermission catalog 항목
StaffPermissionstaff_permissionspermission code를 staff ID에 할당하는 composite-key row

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

  • Staff.role은 staff row에 직접 저장되지 않습니다. auth 모듈의 users table을 대상으로 한 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는 자기 자신을 관리할 수 없고, 역할 계층은 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존재 이유
AuthServiceauth user 생성/삭제, 비밀번호 재설정, login ID 변경
BillingServiceMetricType.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하지는 않습니다.

관측성과 할당량 동작

  • 일반 lifecycle 및 permission 쓰기는 StaffCommandService를 통해 로그를 남깁니다.
  • owner bootstrap은 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 오류 없이 완료됩니다.

관련 페이지