47 lines
2.2 KiB
TypeScript
47 lines
2.2 KiB
TypeScript
// "Export my data": every installed module's share of the account, composed into one document.
|
|
//
|
|
// Each module that keeps something about the person contributes an export to kit-tenancy's
|
|
// `tenancy.account` point -- this module's own rows among them -- and every one of them is a section.
|
|
// A module that fails is a `failed` section and the export is `complete: false`, naming it; a module
|
|
// that can erase but not export is `not_exported`. Nothing is left out without saying so (joint
|
|
// contract §12).
|
|
|
|
import { AppError, notFound } from "@conusai-kit/core"
|
|
import { ACCOUNT_EXPORT_FORMAT } from "../contract/constants.js"
|
|
import type { AccountExport, ExportSection } from "../contract/procedures.js"
|
|
import type { AccountDeps } from "./ports.js"
|
|
|
|
/** The problem code of a refusal, and whether trying again later may help. Never its detail. */
|
|
export function refusalOf(error: unknown): { code: string; retryable: boolean } {
|
|
if (error instanceof AppError) return { code: error.code, retryable: error.status >= 500 || error.status === 429 }
|
|
return { code: "unavailable", retryable: true }
|
|
}
|
|
|
|
export async function exportAccount(deps: AccountDeps, userId: string): Promise<AccountExport> {
|
|
if ((await deps.store.emailOf(userId)) === null) throw notFound("no such user")
|
|
const sections = await Promise.all(
|
|
deps.contributions.map(async (contribution): Promise<[string, ExportSection]> => {
|
|
if (!contribution.export) return [contribution.key, { status: "not_exported" }]
|
|
try {
|
|
const data = await contribution.export({
|
|
subject: userId,
|
|
container: deps.container,
|
|
...(deps.signal ? { signal: deps.signal } : {}),
|
|
})
|
|
return [contribution.key, { status: "ok", data }]
|
|
} catch (error) {
|
|
return [contribution.key, { status: "failed", ...refusalOf(error) }]
|
|
}
|
|
}),
|
|
)
|
|
const failed = sections.filter(([, section]) => section.status === "failed").map(([key]) => key)
|
|
return {
|
|
format: ACCOUNT_EXPORT_FORMAT,
|
|
exportedAt: new Date(deps.now()).toISOString(),
|
|
subject: userId,
|
|
complete: sections.every(([, section]) => section.status === "ok"),
|
|
failed,
|
|
sections: Object.fromEntries(sections),
|
|
}
|
|
}
|