68f85f3736
- 첫 로그인 비번 강제 변경 (RUN_082): FORCE_PASSWORD_CHANGE 컬럼, ForcePasswordChangeGuardFilter, /auth/change-password API + 페이지 - 테넌트 일관성 가드: TenantConsistencyGuardFilter 로 JWT.company_code ↔ 서브도메인 company_code 대조, CompanyResolver 가 (db_name, company_code) 동시 반환 - 회사 관리 확장 (RUN_083 audit log, RUN_084 lifecycle 컬럼): CompanyAdmin/Members/Templates/Lifecycle/AuditLog 서비스 + CompanyMgmtController + SuperAdminGuard - 회사 관리 UI: CompanyAccordionRow 탭화 + 모달 4종 (AdminInfo/Deactivate/Delete/RecopyTemplates) + AuditLogDrawer + csvExport - 프로비저닝 마법사: force_password_change 토글 반영 - 프론트 인증: storage 이벤트 멀티탭 동기화, 403 errorCode (PASSWORD_CHANGE_REQUIRED / CROSS_TENANT_REJECTED / TENANT_NOT_RESOLVED) 전역 리다이렉트 - 기타: StartupSchemaMigrator, OS별 도커 기동 스크립트, CLAUDE.md 트래킹 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
5.1 KiB
TypeScript
152 lines
5.1 KiB
TypeScript
/**
|
|
* Phase 3-A 프로비저닝 API 클라이언트.
|
|
* 백엔드: /api/admin/provisioning/*
|
|
* - GET /table-groups - 마법사 Step 2 체크박스 렌더
|
|
* - GET /check - subdomain/db_prefix/company_code 실시간 검증
|
|
* - POST /companies - 회사 생성 (202 accepted)
|
|
* - GET /status/{id} - 진행 상태 폴링
|
|
* - GET /companies-stats - 메인 화면 accordion 렌더용 (derived 필드 포함)
|
|
*/
|
|
import { apiClient } from "./client";
|
|
|
|
export type CompanyStats = Record<string, any>;
|
|
|
|
export async function getCompaniesStats(): Promise<CompanyStats[]> {
|
|
const { data } = await apiClient.get("/admin/provisioning/companies-stats");
|
|
return Array.isArray(data) ? data : [];
|
|
}
|
|
|
|
export async function getTableGroups(): Promise<Record<string, any>[]> {
|
|
const { data } = await apiClient.get("/admin/provisioning/table-groups");
|
|
return Array.isArray(data) ? data : [];
|
|
}
|
|
|
|
export interface CheckParams {
|
|
subdomain?: string;
|
|
dbPrefix?: string;
|
|
companyCode?: string;
|
|
}
|
|
|
|
export async function checkAvailability(params: CheckParams): Promise<Record<string, any>> {
|
|
const q = new URLSearchParams();
|
|
if (params.subdomain) q.set("subdomain", params.subdomain);
|
|
if (params.dbPrefix) q.set("dbPrefix", params.dbPrefix);
|
|
if (params.companyCode) q.set("companyCode", params.companyCode);
|
|
const { data } = await apiClient.get(`/admin/provisioning/check?${q.toString()}`);
|
|
return data || {};
|
|
}
|
|
|
|
export interface CreateCompanyRequest {
|
|
company_code: string;
|
|
company_name: string;
|
|
subdomain: string;
|
|
db_prefix: string;
|
|
business_registration_number?: string;
|
|
representative_name?: string;
|
|
representative_phone?: string;
|
|
email?: string;
|
|
website?: string;
|
|
address?: string;
|
|
selected_groups?: string[];
|
|
initial_password?: string;
|
|
force_password_change?: boolean;
|
|
}
|
|
|
|
export interface CreateCompanyResponse {
|
|
provisioning_id: string;
|
|
company_code: string;
|
|
db_name: string;
|
|
subdomain: string;
|
|
admin_user_id: string;
|
|
initial_password: string;
|
|
status_url: string;
|
|
}
|
|
|
|
export async function createCompany(req: CreateCompanyRequest): Promise<CreateCompanyResponse> {
|
|
const { data } = await apiClient.post("/admin/provisioning/companies", req);
|
|
return data;
|
|
}
|
|
|
|
export async function getProvisioningStatus(jobId: string): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/status/${jobId}`);
|
|
return data || {};
|
|
}
|
|
|
|
// ───────────────── 회사 관리 (lifecycle / admin / members / templates / audit) ─────────────────
|
|
|
|
export async function getCompanyAdmin(companyCode: string): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/companies/${companyCode}/admin`);
|
|
return data || {};
|
|
}
|
|
|
|
export interface ResetAdminPasswordResponse {
|
|
admin_user_id?: string;
|
|
new_password?: string;
|
|
force_password_change?: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
export async function resetAdminPassword(companyCode: string): Promise<ResetAdminPasswordResponse> {
|
|
const { data } = await apiClient.post(`/admin/provisioning/companies/${companyCode}/admin/reset-password`);
|
|
return data || {};
|
|
}
|
|
|
|
export async function getCompanyMembers(companyCode: string): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/companies/${companyCode}/members`);
|
|
return data || {};
|
|
}
|
|
|
|
export async function getInstalledGroups(companyCode: string): Promise<Record<string, any>[]> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/companies/${companyCode}/installed-groups`);
|
|
return Array.isArray(data) ? data : [];
|
|
}
|
|
|
|
export async function recopyTemplates(companyCode: string, selectedGroups: string[]): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.post(`/admin/provisioning/companies/${companyCode}/re-copy`, {
|
|
selected_groups: selectedGroups,
|
|
});
|
|
return data || {};
|
|
}
|
|
|
|
export async function patchCompanyStatus(
|
|
companyCode: string,
|
|
status: "active" | "suspended",
|
|
reason?: string,
|
|
): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.patch(`/admin/provisioning/companies/${companyCode}/status`, {
|
|
status,
|
|
reason,
|
|
});
|
|
return data || {};
|
|
}
|
|
|
|
/** 영구 삭제 — 서브도메인 타이핑 확인 필수 */
|
|
export async function deleteCompany(companyCode: string, confirmSubdomain: string): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.delete(`/admin/provisioning/companies/${companyCode}`, {
|
|
data: { confirm_subdomain: confirmSubdomain },
|
|
});
|
|
return data || {};
|
|
}
|
|
|
|
export async function getCompanyAuditLog(
|
|
companyCode: string,
|
|
page = 1,
|
|
limit = 50,
|
|
): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/companies/${companyCode}/audit-log`, {
|
|
params: { page, limit },
|
|
});
|
|
return data || {};
|
|
}
|
|
|
|
export async function getGlobalAuditLog(
|
|
page = 1,
|
|
limit = 50,
|
|
action?: string,
|
|
): Promise<Record<string, any>> {
|
|
const { data } = await apiClient.get(`/admin/provisioning/audit-log`, {
|
|
params: { page, limit, action },
|
|
});
|
|
return data || {};
|
|
}
|