# 기존 관리자 비즈니스 규칙 작성일: 2026-06-09 ## 1. 개요 이 문서는 기존 `mouse-typing` 관리자 기능을 조사해 신규 `chocoadmin`에서 재현해야 할 비즈니스 규칙을 정리한 것이다. 조사 대상: - 기존 서비스: `/Users/jisangs/work/jinaju/git/web/mouse-typing` - 관리자 화면: `src/web/admin/` - 관리자 서버: `src/web/server/admin/` - 공통 로그 유틸: `src/web/server/lib/maestro_log.php` - 계정 표시/계산 유틸: `src/web/js/lib/account_info.js`, `src/web/js/maestro_available_date.js` 중요 결론: - 관리자 비밀번호는 MariaDB `PASSWORD()` 함수로 검증한다. - 연장 승인은 기존 만료일 기준 1년 연장하되, 만료된 계정은 오늘 기준 1년 연장한다. - 기존 연장 승인 서버는 날짜를 직접 계산하지 않고 화면에서 전달한 `new_available_date`를 저장한다. - 업그레이드 승인은 `AccountType`, `ActivateStatus`, `AvailableActivateDateTime`만 변경하고 `PlayerCount`는 변경하지 않는다. - 기존 관리자에는 연장 취소/업그레이드 거절 버튼이 없다. 승인 처리 중 같은 마에스트로의 다른 요청을 `Status=2`로 닫는 동작만 있다. - 기존 변경 처리에는 명시적 트랜잭션이 없다. ## 2. 기존 관리자 파일 맵 | 기존 화면 | 서버 파일 | 역할 | 신규 `chocoadmin` 매핑 | |---|---|---|---| | `admin/login.html` | `server/admin/login.php` | 관리자 로그인 | `/login`, NextAuth Credentials Provider | | `admin/admin_all_list.html` | `server/admin/maestro_all_list.php` | 전체 마에스트로 목록 조회 | `/maestros`, `GET /api/maestros` | | `admin/admin_register_list.html` | `server/admin/maestro_registered_list.php`, `server/admin/register_maestro.php` | 미승인 마에스트로 조회 및 등록 승인 | MVP 외 또는 상세 보조 규칙 | | `admin/admin_extension_list.html` | `server/admin/maestro_extension_list.php`, `server/admin/extension_maestro.php` | 연장 요청 조회 및 승인 | `/extension-requests`, `PATCH /api/extension-requests/[id]` | | `admin/admin_upgrade_list.html` | `server/admin/maestro_upgrade_list.php`, `server/admin/upgrade_maestro.php` | 업그레이드 요청 조회 및 승인 | `/upgrade-requests`, `PATCH /api/upgrade-requests/[id]` | | `admin/admin_expiration_list.html` | 없음 | 정적 골격만 존재, 실제 조회 로직 없음 | MVP 제외 | | `admin/admin_send_email.html` | `server/mail/send_mail_test.php` | 테스트 메일 발송 화면 | MVP 제외 | | `admin/admin_header.html` | 없음 | 관리자 메뉴 링크 | 공통 사이드바/헤더 | ## 3. 상태값 코드표 ### `maestro.AccountType` | 값 | 의미 | 정원 | 금액 | |---:|---|---:|---:| | 1 | 20명 | 20 | 10,000 | | 2 | 50명 | 50 | 20,000 | | 3 | 100명 | 100 | 30,000 | | 4 | 500명 | 500 | 40,000 | | 5 | 1,000명 | 1,000 | 50,000 | | 100 | 학생체험 | - | - | | 101 | 마에체험 | - | - | ### `maestro.ActivateStatus` | 값 | 기존 표시 | 의미 | |---:|---|---| | 0 | 미승인 / 입금대기 | 가입 후 관리자 승인 전 | | 1 | 체험 | 체험 계정 | | 2 | 활성화 | 유료 활성 계정 | | 100 | 등록 취소 / 환불 | 등록 취소 또는 환불 상태 | ### `maestro_extension.Status` | 값 | 기존 표시 | 의미 | |---:|---|---| | 1 | 요청 | 연장 요청 | | 2 | 취소 | 닫힌 요청 | | 3 | 업그레이드 적용 | 코드상 공통 표시 문구이나 실제 의미는 연장 적용 | ### `maestro_upgrade.Status` | 값 | 기존 표시 | 의미 | |---:|---|---| | 1 | 요청 | 업그레이드 요청 | | 2 | 취소 | 닫힌 요청 | | 3 | 업그레이드 적용 | 업그레이드 적용 | ## 4. 로그인/관리자 인증 규칙 기존 흐름: 1. `login.html`에서 `admin_name`, `password`를 `server/admin/login.php`로 POST한다. 2. `get_admin_id($adminName)`에서 `admin.Name = ?` 조건으로 관리자 존재 여부를 먼저 확인한다. 3. `login($adminName, $password)`에서 `admin.Name = ? AND admin.Password = PASSWORD(?)` 조건으로 검증한다. 4. 응답 데이터로 `adminID`, `accountType`, `activateStatus`를 반환한다. 실패 조건: | 조건 | error_code | message | |---|---|---| | `Name`에 해당하는 관리자 없음 | `not_registered_id` | 등록되지 않은 서비스 관리자 아이디입니다. | | 비밀번호 검증 결과 없음 | `no_data` 또는 `wrong_password` | 로그인하지 못했습니다. / 비밀번호가 틀렸습니다. | | `ActivateStatus === 0` | `not_activated` | 서비스 관리자 계정이 아직 활성화되지 않았습니다. | 신규 구현 규칙: - 기존 계정 호환을 위해 최초 구현은 MariaDB `PASSWORD()` 함수를 이용해 검증한다. - NextAuth 세션에는 최소 `adminID`, `accountType`, `activateStatus`를 포함한다. - `ActivateStatus = 0` 관리자는 로그인시키지 않는다. - `PASSWORD()`는 보안상 낡은 방식이므로, 해시 업그레이드는 별도 마이그레이션 과제로 분리한다. ## 5. 마에스트로 목록 조회 규칙 ### 전체 목록 기존 서버: `server/admin/maestro_all_list.php` 검색어가 없을 때: - 테이블: `maestro` - 컬럼: `MaestroID`, `Name`, `AccountType`, `ActivateStatus`, `AvailableActivateDateTime`, `AcceptClausesDateTime`, `PlayerCount` - 정렬: `MaestroID DESC` 검색어가 있을 때: - 조건: `Name LIKE CONCAT('%', ?, '%')` - 정렬: `MaestroID DESC` - 주의: 기존 검색 쿼리는 `AvailableActivateDateTime`을 조회하지 않는데 화면은 해당 값을 사용한다. 신규 구현에서는 전체/검색 결과 모두 동일 컬럼을 반환해야 한다. 기존 화면 표시: - 상태 텍스트는 `accountStatus(accountType, activateStatus)`로 계산한다. - `AvailableActivateDateTime`이 `0`으로 시작하면 빈 문자열로 표시한다. - 비활성화 버튼은 disabled 상태이며 동작하지 않는다. 신규 구현 규칙: - `Name`, `Email`, `MaestroID` 검색을 지원하되 기존 기본 정렬은 `MaestroID DESC`로 시작한다. - 전체/검색 결과의 반환 컬럼 차이를 만들지 않는다. ### 미승인 등록 목록 기존 서버: `server/admin/maestro_registered_list.php` - 테이블: `maestro` - 조건: `ActivateStatus = 0` - 검색 조건: `Name LIKE CONCAT('%', ?, '%') AND ActivateStatus = 0` - 컬럼: `MaestroID`, `Name`, `AccountType`, `AcceptClausesDateTime` - 정렬: `MaestroID DESC` ## 6. 등록/활성화 처리 규칙 기존 서버: `server/admin/register_maestro.php` 처리 순서: 1. `maestro.ActivateStatus = 1`로 변경한다. 2. `maestro.AvailableActivateDateTime = '2022-07-31 23:59:59'`로 변경한다. 3. `player`에 테스트 플레이어 `Name='마에스트로'`, `EnterCode=''`, `AccountType=1`을 추가한다. 4. 추가된 테스트 플레이어를 `maestro.MaestroTestID`에 저장한다. 5. `app.Status = 1`인 앱을 모두 `active_app`에 추가한다. 6. 샘플 학생 4명을 `player.AccountType = 0`으로 추가한다. 7. `player.AccountType = 0` 학생 수를 세어 `maestro.PlayerCount`에 저장한다. 8. 등록 완료 메일을 발송한다. 9. `maestro_log`에 `register_maestro` 로그를 기록한다. 주의: - 코드에는 `DATE_ADD(NOW(), INTERVAL 1 YEAR)`와 `DATE_ADD(NOW(), INTERVAL 14 DAY)`가 주석으로 남아 있지만 실제 실행 쿼리는 고정일 `2022-07-31 23:59:59`다. - 현재 MVP의 핵심 변경 기능은 연장/업그레이드이므로 등록 승인은 별도 범위로 둘 수 있다. ## 7. 연장 신청 조회/승인 규칙 ### 요청 생성 기존 서버: `server/maestro/request_extension_maestro.php` - `maestro_extension`에 `AccountType`, `RequestedDateTime = NOW()`, `Status = 1`, `MaestroID`를 INSERT한다. - 같은 마에스트로의 기존 요청 중복 여부는 검사하지 않는다. - INSERT 후 `MaestroID` 기준 전체 연장 요청 수가 1개 이상인지 단순 확인한다. - 입금 안내 메일을 발송한다. ### 관리자 목록 조회 기존 서버: `server/admin/maestro_extension_list.php` 검색어가 없을 때: - 테이블: `maestro_extension E`, `maestro M` - 조건: `E.Status < 2 AND E.MaestroID = M.MaestroID` - 컬럼: `E.MaestroExtensionID`, `E.MaestroID`, `M.Name`, `E.AccountType`, `E.RequestedDateTime`, `M.AvailableActivateDateTime`, `E.Status` - 정렬: `E.MaestroExtensionID DESC` 검색어가 있을 때: - 조건: `M.Name LIKE CONCAT('%', ?, '%') AND E.MaestroID = M.MaestroID` - 주의: 검색 쿼리에는 `E.Status < 2` 조건이 없다. 신규 구현에서는 기본 정책에 맞게 상태 필터를 명시적으로 제공해야 한다. ### 관리자 승인 기존 화면: `admin/admin_extension_list.html` - `availableActivateDateTime`의 날짜 부분을 기준으로 새 만료일을 계산한다. - 계산 유틸: `src/web/js/maestro_available_date.js` - 현재 만료일이 지났으면 오늘 기준 1년 후 날짜를 사용한다. - 현재 만료일이 지나지 않았으면 기존 만료일 기준 1년 후 날짜를 사용한다. - 서버에는 `new_available_date` 문자열을 POST한다. 기존 서버: `server/admin/extension_maestro.php` 처리 순서: 1. `maestro.ActivateStatus = 2`, `maestro.AvailableActivateDateTime = DATE_FORMAT(?, '%Y-%m-%d')`로 변경한다. 2. 같은 `MaestroID`의 `maestro_extension.Status = 1` 요청을 모두 `Status = 2`로 변경한다. 3. 선택한 `MaestroExtensionID` 요청을 `Status = 3`으로 변경한다. 4. 연장 완료 메일을 발송한다. 5. `maestro_log`에 `extension_maestro` 로그를 기록한다. 로그: - `Type`: `extension_maestro` - `Remark`: `{maestro_name}({registered_account_type})` 신규 구현 규칙: - 날짜 계산은 클라이언트가 아니라 서버에서 수행한다. - 기존 규칙과 동일하게 만료 전이면 기존 만료일 기준 +1년, 만료 후면 현재일 기준 +1년으로 계산한다. - `AvailableActivateDateTime` 저장 시 기존 서버는 날짜만 저장하므로 시간은 DB 기본 파싱 결과를 고려해야 한다. 신규 구현에서는 일관성을 위해 `YYYY-MM-DD 00:00:00` 또는 기존 호환 형식을 명시한다. - 승인 전 선택한 요청이 `Status = 1`인지 확인한다. - 트랜잭션 안에서 `maestro`, `maestro_extension`, `maestro_log`를 함께 처리한다. - 같은 마에스트로의 다른 `Status = 1` 요청을 `Status = 2`로 닫은 뒤 선택 요청을 `Status = 3`으로 적용하는 기존 동작을 재현한다. ## 8. 업그레이드 신청 조회/승인 규칙 ### 요청 생성 기존 서버: `server/maestro/request_upgrade_maestro.php` - `maestro_upgrade`에 `RegisteredActivateStatus`, `RegisteredAccountType`, `RequestedAccountType`, `RequestedDateTime = NOW()`, `Status = 1`, `MaestroID`를 INSERT한다. - 같은 마에스트로의 기존 요청 중복 여부는 검사하지 않는다. - INSERT 후 `MaestroID` 기준 전체 업그레이드 요청 수가 1개 이상인지 단순 확인한다. - 체험 계정이면 체험 업그레이드 입금 안내 메일, 그 외에는 일반 업그레이드 입금 안내 메일을 발송한다. ### 관리자 목록 조회 기존 서버: `server/admin/maestro_upgrade_list.php` 검색어가 없을 때: - 테이블: `maestro_upgrade U`, `maestro M` - 조건: `U.Status < 2 AND U.MaestroID = M.MaestroID` - 컬럼: `U.MaestroUpgradeID`, `U.MaestroID`, `M.Name`, `U.RegisteredActivateStatus`, `U.RegisteredAccountType`, `U.RequestedAccountType`, `U.RequestedDateTime`, `M.AvailableActivateDateTime`, `U.Status` - 정렬: `U.MaestroUpgradeID DESC` 검색어가 있을 때: - 조건: `M.Name LIKE CONCAT('%', ?, '%') AND U.MaestroID = M.MaestroID` - 주의: 검색 쿼리에는 `U.Status < 2` 조건이 없다. 신규 구현에서는 상태 필터를 명시적으로 제공해야 한다. ### 관리자 승인 기존 화면: `admin/admin_upgrade_list.html` - `Status = 1`인 요청만 승인 버튼을 표시한다. - 체험 계정(`RegisteredActivateStatus = 1`)은 기존 요금제를 `trialAccountType`으로 표시하고 추가 금액은 요청 요금제 전체 금액으로 계산한다. - 그 외 계정은 추가 금액을 `RequestedAccountType` 금액 - `RegisteredAccountType` 금액으로 계산한다. 기존 서버: `server/admin/upgrade_maestro.php` 처리 순서: 1. `maestro.AccountType = upgrade_account_type`으로 변경한다. 2. `maestro.ActivateStatus = 2`로 변경한다. 3. `maestro.AvailableActivateDateTime = DATE_ADD(NOW(), INTERVAL 1 YEAR)`로 변경한다. 4. 같은 `MaestroID`의 `maestro_upgrade.Status = 1` 요청을 모두 `Status = 2`로 변경한다. 5. 선택한 `MaestroUpgradeID` 요청을 `Status = 3`으로 변경한다. 6. 체험 계정이면 체험 업그레이드 완료 메일, 그 외에는 일반 업그레이드 완료 메일을 발송한다. 7. `maestro_log`에 `upgrade_maestro` 로그를 기록한다. 로그: - `Type`: `upgrade_maestro` - `Remark`: `{registered_account_type} -> {upgrade_account_type}` 확정 사항: - `PlayerCount`는 변경하지 않는다. - `AvailableActivateDateTime`은 기존 만료일 기준이 아니라 승인 시점 `NOW()` 기준 1년 후로 설정한다. 신규 구현 규칙: - 승인 전 선택한 요청이 `Status = 1`인지 확인한다. - 트랜잭션 안에서 `maestro`, `maestro_upgrade`, `maestro_log`를 함께 처리한다. - 같은 마에스트로의 다른 `Status = 1` 요청을 `Status = 2`로 닫은 뒤 선택 요청을 `Status = 3`으로 적용하는 기존 동작을 재현한다. - `PlayerCount`는 업그레이드 승인에서 변경하지 않는다. ## 9. 취소/거절 규칙 기존 관리자 UI/서버에서 확인된 사실: - 연장 신청에 대한 별도 취소 버튼이나 취소 API는 없다. - 업그레이드 신청에 대한 별도 거절 버튼이나 거절 API는 없다. - `Status = 2`는 승인 처리 중 같은 마에스트로의 다른 미처리 요청을 닫기 위해 사용된다. - 화면 유틸 `status()`는 `Status = 2`를 "취소"로 표시하지만, 관리자가 명시적으로 취소/거절하는 흐름은 구현되어 있지 않다. 신규 구현 판단: - `docs/plan/project-plan.md`의 취소/거절 기능은 기존 관리자 재현이 아니라 신규 보완 기능이다. - 취소/거절 API를 만들 경우 `Status = 1`인 단일 요청만 `Status = 2`로 변경하고 `maestro_log`에 별도 로그를 남기는 방식으로 정의해야 한다. - 기존 `maestro_log.Type`에는 취소/거절 전용 타입이 없으므로 신규 타입을 도입할지 기존 타입에 `Remark`로 표현할지 결정이 필요하다. 권장 신규 로그 타입: - `cancel_extension_maestro` - `reject_upgrade_maestro` ## 10. `maestro_log` 기록 규칙 공통 함수: `src/web/server/lib/maestro_log.php` ```sql INSERT INTO maestro_log (Type, MaestroID, LogDateTime, Remark) VALUES (?, ?, NOW(), ?) ``` 확인된 로그 타입: | Type | 발생 위치 | Remark 형식 | |---|---|---| | `add_maestro` | `server/maestro/add_maestro.php` | `{maestro_name} / {email} / {account_type}` | | `register_maestro` | `server/admin/register_maestro.php` | `{name} / {email}` | | `extension_maestro` | `server/admin/extension_maestro.php` | `{maestro_name}({registered_account_type})` | | `upgrade_maestro` | `server/admin/upgrade_maestro.php` | `{registered_account_type} -> {upgrade_account_type}` | | `update_maestro_name` | `server/maestro/update_maestro_info.php` | `{previous_name} -> {new_name}` | | `update_maestro_email` | `server/maestro/update_maestro_info.php` | `{previous_email} -> {new_email}` | 확인 결과: - `update_maestro` 타입은 코드 검색에서 확인되지 않았다. - 로그에는 관리자 ID나 관리자 이름이 포함되지 않는다. - 로그 시간은 DB 서버의 `NOW()`를 사용한다. - `Remark` 컬럼은 `char(100)`이므로 신규 구현에서도 길이 초과에 주의한다. ## 11. 신규 `chocoadmin` 구현 시 반드시 지켜야 할 규칙 - 운영 DB 스키마는 변경하지 않는다. - 관리자 로그인은 기존 `admin.Name`과 `admin.Password = PASSWORD(?)` 호환 검증을 지원한다. - 모든 변경 API는 서버에서 현재 상태를 다시 조회하고 `Status = 1`인지 확인한다. - 승인/취소/거절 같은 변경 작업은 트랜잭션으로 묶는다. - 연장 승인은 서버에서 새 만료일을 계산한다. - 업그레이드 승인은 `PlayerCount`를 변경하지 않는다. - 연장/업그레이드 승인 시 같은 마에스트로의 다른 요청을 `Status = 2`로 닫고 선택 요청을 `Status = 3`으로 적용한다. - 기존 목록 검색 쿼리의 상태 필터 누락은 그대로 복제하지 말고 명시적 필터로 보완한다. - 메일 발송은 트랜잭션 커밋 이후 수행하는 것이 안전하다. 기존 코드는 DB 변경 후 메일을 발송한다. ## 12. 기존 코드에는 없지만 신규 구현에서 보완할 사항 - 명시적 트랜잭션: 기존 관리자 변경 로직에는 `BEGIN`, `COMMIT`, `ROLLBACK`이 없다. - 중복 처리 방지: 기존 승인 서버는 요청의 현재 `Status`를 먼저 검증하지 않는다. - 서버 사이드 날짜 계산: 기존 연장 승인은 클라이언트 계산값을 신뢰한다. - 검색 필터 일관성: 연장/업그레이드 검색 쿼리에 `Status < 2` 조건이 빠져 있다. - 목록 컬럼 일관성: 전체 마에스트로 검색 쿼리에 `AvailableActivateDateTime`이 빠져 있다. - 취소/거절 정의: 기존에는 별도 액션이 없으므로 신규 기능으로 명세화해야 한다. - 감사성 강화: 기존 `maestro_log`에는 관리자 식별자가 없으므로 세션 기반 감사 로그가 필요하면 별도 설계가 필요하다. 단, DB 스키마 변경 없이 구현하려면 `Remark`에 관리자 정보를 포함하는 방식만 가능하다.