355 lines
17 KiB
Markdown
355 lines
17 KiB
Markdown
# 기존 관리자 비즈니스 규칙
|
|
|
|
작성일: 2026-06-09
|
|
|
|
## 1. 개요
|
|
|
|
이 문서는 기존 `mouse-typing` 관리자 기능을 조사해 신규 `choco-admin`에서 재현해야 할 비즈니스 규칙을 정리한 것이다.
|
|
|
|
조사 대상:
|
|
|
|
- 기존 서비스: `/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. 기존 관리자 파일 맵
|
|
|
|
| 기존 화면 | 서버 파일 | 역할 | 신규 `choco-admin` 매핑 |
|
|
|---|---|---|---|
|
|
| `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. 신규 `choco-admin` 구현 시 반드시 지켜야 할 규칙
|
|
|
|
- 운영 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`에 관리자 정보를 포함하는 방식만 가능하다.
|