mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Add admin compliance acknowledgement gate
This commit is contained in:
@@ -143,6 +143,53 @@ describe('API Client', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('部署与运营合规未确认时广播事件且保留登录态', async () => {
|
||||
localStorage.setItem('auth_token', 'admin-token')
|
||||
const listener = vi.fn()
|
||||
window.addEventListener('admin-compliance-required', listener)
|
||||
|
||||
const adapter = vi.fn().mockRejectedValue({
|
||||
response: {
|
||||
status: 423,
|
||||
data: {
|
||||
code: 'ADMIN_COMPLIANCE_ACK_REQUIRED',
|
||||
message: 'administrator compliance acknowledgement is required',
|
||||
metadata: {
|
||||
version: 'v2026.06.10',
|
||||
document_path_zh: 'docs/legal/admin-compliance.zh.md',
|
||||
document_path_en: 'docs/legal/admin-compliance.en.md',
|
||||
},
|
||||
},
|
||||
},
|
||||
config: {
|
||||
url: '/admin/users',
|
||||
headers: { Authorization: 'Bearer admin-token' },
|
||||
},
|
||||
code: 'ERR_BAD_REQUEST',
|
||||
})
|
||||
apiClient.defaults.adapter = adapter
|
||||
|
||||
await expect(apiClient.get('/admin/users')).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
status: 423,
|
||||
code: 'ADMIN_COMPLIANCE_ACK_REQUIRED',
|
||||
metadata: expect.objectContaining({
|
||||
version: 'v2026.06.10',
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1)
|
||||
expect((listener.mock.calls[0][0] as CustomEvent).detail).toEqual(
|
||||
expect.objectContaining({
|
||||
version: 'v2026.06.10',
|
||||
})
|
||||
)
|
||||
expect(localStorage.getItem('auth_token')).toBe('admin-token')
|
||||
|
||||
window.removeEventListener('admin-compliance-required', listener)
|
||||
})
|
||||
})
|
||||
|
||||
// --- 401 Token 刷新 ---
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { apiClient } from '@/api/client'
|
||||
|
||||
export interface AdminComplianceAcknowledgement {
|
||||
version: string
|
||||
document_zh: string
|
||||
document_en: string
|
||||
admin_user_id: number
|
||||
ip_address?: string
|
||||
user_agent?: string
|
||||
accepted_at: string
|
||||
}
|
||||
|
||||
export interface AdminComplianceStatus {
|
||||
required: boolean
|
||||
version: string
|
||||
document_path_zh: string
|
||||
document_path_en: string
|
||||
document_url_zh: string
|
||||
document_url_en: string
|
||||
ack_phrase_zh: string
|
||||
ack_phrase_en: string
|
||||
acknowledgement?: AdminComplianceAcknowledgement
|
||||
}
|
||||
|
||||
export interface AcceptAdminComplianceRequest {
|
||||
phrase: string
|
||||
language: string
|
||||
}
|
||||
|
||||
export const adminComplianceAPI = {
|
||||
async getStatus(): Promise<AdminComplianceStatus> {
|
||||
const { data } = await apiClient.get<AdminComplianceStatus>('/admin/compliance')
|
||||
return data
|
||||
},
|
||||
|
||||
async accept(payload: AcceptAdminComplianceRequest): Promise<AdminComplianceStatus> {
|
||||
const { data } = await apiClient.post<AdminComplianceStatus>('/admin/compliance/accept', payload)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
export default adminComplianceAPI
|
||||
@@ -31,6 +31,7 @@ import channelMonitorTemplateAPI from './channelMonitorTemplate'
|
||||
import adminPaymentAPI from './payment'
|
||||
import affiliatesAPI from './affiliates'
|
||||
import riskControlAPI from './riskControl'
|
||||
import adminComplianceAPI from './compliance'
|
||||
|
||||
/**
|
||||
* Unified admin API object for convenient access
|
||||
@@ -63,7 +64,8 @@ export const adminAPI = {
|
||||
channelMonitorTemplate: channelMonitorTemplateAPI,
|
||||
payment: adminPaymentAPI,
|
||||
affiliates: affiliatesAPI,
|
||||
riskControl: riskControlAPI
|
||||
riskControl: riskControlAPI,
|
||||
compliance: adminComplianceAPI
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -94,7 +96,8 @@ export {
|
||||
channelMonitorTemplateAPI,
|
||||
adminPaymentAPI,
|
||||
affiliatesAPI,
|
||||
riskControlAPI
|
||||
riskControlAPI,
|
||||
adminComplianceAPI
|
||||
}
|
||||
|
||||
export default adminAPI
|
||||
|
||||
@@ -148,6 +148,23 @@ apiClient.interceptors.response.use(
|
||||
})
|
||||
}
|
||||
|
||||
if (status === 423 && apiData.code === 'ADMIN_COMPLIANCE_ACK_REQUIRED') {
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent('admin-compliance-required', {
|
||||
detail: apiData.metadata || {}
|
||||
}))
|
||||
} catch {
|
||||
// ignore event failures
|
||||
}
|
||||
|
||||
return Promise.reject({
|
||||
status,
|
||||
code: apiData.code,
|
||||
message: apiData.message || error.message,
|
||||
metadata: apiData.metadata,
|
||||
})
|
||||
}
|
||||
|
||||
// 401: Try to refresh the token if we have a refresh token
|
||||
// This handles TOKEN_EXPIRED, INVALID_TOKEN, TOKEN_REVOKED, etc.
|
||||
if (status === 401 && !originalRequest._retry) {
|
||||
|
||||
Reference in New Issue
Block a user