117 lines
4.7 KiB
TypeScript
117 lines
4.7 KiB
TypeScript
import type { CloudflareApi } from './cloudflare-api.ts';
|
|
import type { Database, DeploymentSettings } from './deployment-config.ts';
|
|
|
|
export interface DeploymentWorkspaceState {
|
|
accountId: string;
|
|
authProvider?: 'cloudflare' | 'github';
|
|
authRevision: number;
|
|
accessNotBefore: number;
|
|
githubAdminId?: string;
|
|
}
|
|
|
|
interface StateRow {
|
|
account_id: string;
|
|
auth_provider: string;
|
|
auth_revision: number;
|
|
access_not_before: number;
|
|
github_admin_id: string | null;
|
|
}
|
|
|
|
interface QueryResult<T> { success: boolean; results: T[] }
|
|
|
|
function databasePath(settings: DeploymentSettings, database: Database): string {
|
|
return `/accounts/${settings.accountId}/d1/database/${database.uuid}/query`;
|
|
}
|
|
|
|
async function query<T>(
|
|
api: CloudflareApi,
|
|
settings: DeploymentSettings,
|
|
database: Database,
|
|
sql: string,
|
|
params: unknown[] = [],
|
|
): Promise<T[]> {
|
|
const result = await api.request<QueryResult<T>[]>(databasePath(settings, database), 'POST', { sql, params });
|
|
if (result.length !== 1 || !result[0].success) throw new Error('无法读取管理员工作区状态,请重试。');
|
|
return result[0].results;
|
|
}
|
|
|
|
export async function readWorkspaceState(
|
|
api: CloudflareApi,
|
|
settings: DeploymentSettings,
|
|
database: Database,
|
|
): Promise<DeploymentWorkspaceState> {
|
|
const tables = await query<{ name: string }>(api, settings, database,
|
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('hosts', 'workspace_state')");
|
|
const names = new Set(tables.map((table) => table.name));
|
|
const owners = names.has('hosts')
|
|
? await query<{ account_id: string }>(api, settings, database, 'SELECT DISTINCT account_id FROM hosts LIMIT 2') : [];
|
|
if (owners.length > 1) {
|
|
throw new Error('数据库存在多个资料所有者,无法自动转为单管理员;请先明确资料归属。');
|
|
}
|
|
|
|
if (names.has('workspace_state')) {
|
|
const rows = await query<StateRow>(api, settings, database,
|
|
'SELECT account_id, auth_provider, auth_revision, access_not_before, github_admin_id FROM workspace_state WHERE id = 1');
|
|
const row = rows[0];
|
|
if (row) {
|
|
if (row.auth_provider !== 'cloudflare' && row.auth_provider !== 'github') {
|
|
throw new Error('管理员工作区中的认证方式无效,请检查数据库状态。');
|
|
}
|
|
if (owners[0] && owners[0].account_id !== row.account_id) {
|
|
throw new Error('数据库资料所有者与固定管理员工作区不一致,停止自动部署。');
|
|
}
|
|
return {
|
|
accountId: row.account_id,
|
|
authProvider: row.auth_provider,
|
|
authRevision: row.auth_revision,
|
|
accessNotBefore: row.access_not_before,
|
|
githubAdminId: row.github_admin_id ?? undefined,
|
|
};
|
|
}
|
|
}
|
|
|
|
// 只在首次初始化读取旧 hosts;状态落库后,即使删除最后一台主机也不会改变 owner。
|
|
return {
|
|
accountId: owners[0]?.account_id || 'admin',
|
|
authRevision: 0,
|
|
accessNotBefore: 0,
|
|
};
|
|
}
|
|
|
|
export async function updateWorkspaceState(
|
|
api: CloudflareApi,
|
|
settings: DeploymentSettings,
|
|
database: Database,
|
|
previous: DeploymentWorkspaceState,
|
|
githubAdminId?: string,
|
|
now = Math.floor(Date.now() / 1000),
|
|
): Promise<DeploymentWorkspaceState> {
|
|
const rows = await query<StateRow>(api, settings, database,
|
|
'SELECT account_id, auth_provider, auth_revision, access_not_before, github_admin_id FROM workspace_state WHERE id = 1');
|
|
const current = rows[0];
|
|
if (!current) throw new Error('数据库迁移未建立管理员工作区状态,停止部署。');
|
|
if (current.account_id !== previous.accountId) throw new Error('管理员工作区在部署期间发生变化,停止部署。');
|
|
|
|
const retainedGitHubId = settings.authProvider === 'github' ? githubAdminId : (current.github_admin_id ?? undefined);
|
|
if (settings.authProvider === 'github' && !retainedGitHubId) {
|
|
throw new Error('GitHub 管理员数字 ID 尚未固定,停止部署。');
|
|
}
|
|
const providerChanged = current.auth_provider !== settings.authProvider;
|
|
const administratorChanged = settings.authProvider === 'github'
|
|
&& current.github_admin_id !== null && current.github_admin_id !== retainedGitHubId;
|
|
const revision = current.auth_revision + (providerChanged || administratorChanged ? 1 : 0);
|
|
const accessNotBefore = providerChanged ? now + 1 : current.access_not_before;
|
|
await query(api, settings, database,
|
|
`UPDATE workspace_state
|
|
SET auth_provider = ?, auth_revision = ?, access_not_before = ?, github_admin_id = ?
|
|
WHERE id = 1 AND account_id = ?`,
|
|
[settings.authProvider, revision, accessNotBefore, retainedGitHubId ?? null, previous.accountId]);
|
|
return {
|
|
accountId: previous.accountId,
|
|
authProvider: settings.authProvider,
|
|
authRevision: revision,
|
|
accessNotBefore,
|
|
githubAdminId: retainedGitHubId,
|
|
};
|
|
}
|