- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role - Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log - 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록) - Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec - CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세 - recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar - 결제 이력: DB + Payple API 병행 조회 - 권한: super_admin만 위험 작업, admin은 조회 전용 - RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책 - SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
336 lines
13 KiB
JSON
336 lines
13 KiB
JSON
{
|
|
"openapi": "3.0.3",
|
|
"info": {
|
|
"title": "D3RO-VOICE Admin API",
|
|
"description": "Admin CRM Edge Functions for user management, subscription CRUD, payment history, and audit logs.",
|
|
"version": "1.0.0"
|
|
},
|
|
"servers": [
|
|
{
|
|
"url": "https://{supabaseRef}.supabase.co/functions/v1",
|
|
"description": "Production",
|
|
"variables": {
|
|
"supabaseRef": {
|
|
"default": "your-project-ref"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"url": "http://localhost:54321/functions/v1",
|
|
"description": "Local development"
|
|
}
|
|
],
|
|
"security": [
|
|
{
|
|
"BearerAuth": []
|
|
}
|
|
],
|
|
"components": {
|
|
"securitySchemes": {
|
|
"BearerAuth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT",
|
|
"description": "Supabase access_token (admin or super_admin role required)"
|
|
}
|
|
},
|
|
"schemas": {
|
|
"Profile": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "string", "format": "uuid" },
|
|
"name": { "type": "string", "nullable": true },
|
|
"avatar_url": { "type": "string", "nullable": true },
|
|
"locale": { "type": "string" },
|
|
"tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] },
|
|
"role": { "type": "string", "enum": ["user", "admin", "super_admin"] },
|
|
"created_at": { "type": "string", "format": "date-time" },
|
|
"updated_at": { "type": "string", "format": "date-time" }
|
|
}
|
|
},
|
|
"Subscription": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "string", "format": "uuid" },
|
|
"user_id": { "type": "string", "format": "uuid" },
|
|
"tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] },
|
|
"status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] },
|
|
"payment_provider": { "type": "string", "enum": ["none", "stripe", "payple"] },
|
|
"current_period_start": { "type": "string", "format": "date-time", "nullable": true },
|
|
"current_period_end": { "type": "string", "format": "date-time", "nullable": true },
|
|
"overage_credits": { "type": "integer" },
|
|
"admin_note": { "type": "string", "nullable": true },
|
|
"renewal_failures": { "type": "integer" },
|
|
"created_at": { "type": "string", "format": "date-time" },
|
|
"updated_at": { "type": "string", "format": "date-time" }
|
|
}
|
|
},
|
|
"AuditLog": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": { "type": "integer" },
|
|
"admin_id": { "type": "string", "format": "uuid" },
|
|
"admin_name": { "type": "string" },
|
|
"action": { "type": "string" },
|
|
"target_type": { "type": "string" },
|
|
"target_id": { "type": "string", "format": "uuid" },
|
|
"before_data": { "type": "object", "nullable": true },
|
|
"after_data": { "type": "object", "nullable": true },
|
|
"memo": { "type": "string" },
|
|
"created_at": { "type": "string", "format": "date-time" }
|
|
}
|
|
},
|
|
"Error": {
|
|
"type": "object",
|
|
"properties": {
|
|
"error": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"paths": {
|
|
"/admin-users": {
|
|
"get": {
|
|
"tags": ["Users"],
|
|
"summary": "List or get user details",
|
|
"description": "Admin+. Pass userId for single user detail, or omit for paginated list.",
|
|
"parameters": [
|
|
{ "name": "userId", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "Specific user ID for detail view" },
|
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
|
{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
|
{ "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Name search (ilike)" },
|
|
{ "name": "role", "in": "query", "schema": { "type": "string", "enum": ["user", "admin", "super_admin"] } }
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "User list or detail",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"oneOf": [
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"profiles": { "type": "array", "items": { "$ref": "#/components/schemas/Profile" } },
|
|
"total": { "type": "integer" },
|
|
"page": { "type": "integer" },
|
|
"limit": { "type": "integer" }
|
|
}
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"profile": { "$ref": "#/components/schemas/Profile" },
|
|
"subscription": { "$ref": "#/components/schemas/Subscription" }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": { "description": "Not admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
|
|
}
|
|
},
|
|
"patch": {
|
|
"tags": ["Users"],
|
|
"summary": "Change user role",
|
|
"description": "Super admin only. Changes both auth.users.app_metadata.role and profiles.role.",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["userId", "newRole", "memo"],
|
|
"properties": {
|
|
"userId": { "type": "string", "format": "uuid" },
|
|
"newRole": { "type": "string", "enum": ["user", "admin", "super_admin"] },
|
|
"memo": { "type": "string", "description": "Required reason for audit log" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": { "description": "Role changed successfully" },
|
|
"403": { "description": "Not super_admin" }
|
|
}
|
|
}
|
|
},
|
|
"/admin-subscriptions": {
|
|
"get": {
|
|
"tags": ["Subscriptions"],
|
|
"summary": "List or get subscription details",
|
|
"parameters": [
|
|
{ "name": "userId", "in": "query", "schema": { "type": "string", "format": "uuid" } },
|
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
|
{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
|
{ "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] } },
|
|
{ "name": "tier", "in": "query", "schema": { "type": "string", "enum": ["free", "pro", "pro_plus"] } }
|
|
],
|
|
"responses": {
|
|
"200": { "description": "Subscription list or detail" }
|
|
}
|
|
},
|
|
"post": {
|
|
"tags": ["Subscriptions"],
|
|
"summary": "Create subscription (VIP grant / record recovery)",
|
|
"description": "Super admin only.",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["userId", "tier", "memo"],
|
|
"properties": {
|
|
"userId": { "type": "string", "format": "uuid" },
|
|
"tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] },
|
|
"status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"], "default": "active" },
|
|
"currentPeriodEnd": { "type": "string", "format": "date-time" },
|
|
"adminNote": { "type": "string" },
|
|
"memo": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"201": { "description": "Subscription created" },
|
|
"409": { "description": "Subscription already exists" }
|
|
}
|
|
},
|
|
"patch": {
|
|
"tags": ["Subscriptions"],
|
|
"summary": "Update subscription",
|
|
"description": "Super admin only.",
|
|
"parameters": [
|
|
{ "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } }
|
|
],
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["memo"],
|
|
"properties": {
|
|
"tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] },
|
|
"status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] },
|
|
"currentPeriodEnd": { "type": "string", "format": "date-time" },
|
|
"overageCredits": { "type": "integer" },
|
|
"adminNote": { "type": "string" },
|
|
"memo": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": { "description": "Subscription updated" }
|
|
}
|
|
},
|
|
"delete": {
|
|
"tags": ["Subscriptions"],
|
|
"summary": "Soft-delete subscription",
|
|
"description": "Super admin only. Sets status=expired, tier=free.",
|
|
"parameters": [
|
|
{ "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } }
|
|
],
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["memo"],
|
|
"properties": {
|
|
"memo": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": { "description": "Subscription soft-deleted" }
|
|
}
|
|
}
|
|
},
|
|
"/admin-payments": {
|
|
"get": {
|
|
"tags": ["Payments"],
|
|
"summary": "Get payment history for a user",
|
|
"description": "Admin+. Returns DB subscription data + audit logs. Pass source=payple for Payple API history.",
|
|
"parameters": [
|
|
{ "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } },
|
|
{ "name": "source", "in": "query", "schema": { "type": "string", "enum": ["db", "payple"] }, "description": "Add 'payple' to also fetch from Payple API" }
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Payment history",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"subscription": { "$ref": "#/components/schemas/Subscription" },
|
|
"auditLogs": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLog" } },
|
|
"paypleHistory": { "type": "object", "description": "Payple API response (when source=payple)" },
|
|
"paypleError": { "type": "string", "description": "Error message if Payple API call failed" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/admin-audit-log": {
|
|
"get": {
|
|
"tags": ["Audit Log"],
|
|
"summary": "List or get audit log entries",
|
|
"description": "Admin+. Pass id for single entry detail.",
|
|
"parameters": [
|
|
{ "name": "id", "in": "query", "schema": { "type": "integer" }, "description": "Specific log entry ID" },
|
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
|
{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
|
{ "name": "target_type", "in": "query", "schema": { "type": "string", "enum": ["subscription", "profile"] } },
|
|
{ "name": "admin_id", "in": "query", "schema": { "type": "string", "format": "uuid" } },
|
|
{ "name": "target_id", "in": "query", "schema": { "type": "string", "format": "uuid" } },
|
|
{ "name": "from", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Start date (YYYY-MM-DD)" },
|
|
{ "name": "to", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "End date (YYYY-MM-DD)" }
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Audit log list or detail",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"oneOf": [
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"logs": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLog" } },
|
|
"total": { "type": "integer" },
|
|
"page": { "type": "integer" },
|
|
"limit": { "type": "integer" }
|
|
}
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"log": { "$ref": "#/components/schemas/AuditLog" },
|
|
"admin": { "$ref": "#/components/schemas/Profile" }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|