173 lines
4.5 KiB
TypeScript
173 lines
4.5 KiB
TypeScript
import "server-only";
|
|
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import { authorizeHousekeeping } from "../../../foundation/authorization";
|
|
import {
|
|
anyCapability,
|
|
fail,
|
|
type HousekeepingQuery,
|
|
ok,
|
|
} from "../../../foundation/contracts";
|
|
|
|
export interface SystemSettingRow {
|
|
readonly key: string;
|
|
readonly value: string;
|
|
readonly comment?: string | null;
|
|
}
|
|
|
|
export interface SystemEmulatorTextResult {
|
|
readonly rows: readonly SystemSettingRow[];
|
|
readonly total: number;
|
|
}
|
|
|
|
export interface SystemConfigurationAdapters {
|
|
loadWebsiteSettings(): Promise<readonly SystemSettingRow[]>;
|
|
loadEmulatorSettings(): Promise<readonly SystemSettingRow[]>;
|
|
loadEmulatorTexts(): Promise<SystemEmulatorTextResult>;
|
|
}
|
|
|
|
export type SystemConfigurationQueryInput =
|
|
| { readonly routeId: "system.configuration.settings" }
|
|
| { readonly routeId: "system.configuration.emulator" };
|
|
|
|
export type SystemConfigurationQueryData =
|
|
| {
|
|
readonly kind: "settings";
|
|
readonly settings: readonly SystemSettingRow[];
|
|
readonly partialDependencies: readonly string[];
|
|
}
|
|
| {
|
|
readonly kind: "emulator";
|
|
readonly settings: readonly SystemSettingRow[];
|
|
readonly texts: readonly SystemSettingRow[];
|
|
readonly textsTotal: number;
|
|
readonly partialDependencies: readonly string[];
|
|
};
|
|
|
|
const configurationCapability = anyCapability(PERMS.SETTINGS_VIEW);
|
|
|
|
function unavailable(correlationId: string) {
|
|
return fail(
|
|
"DEPENDENCY_UNAVAILABLE",
|
|
"errors.housekeeping.dependencyUnavailable",
|
|
correlationId,
|
|
);
|
|
}
|
|
|
|
export function createSystemConfigurationQuery(
|
|
adapters: SystemConfigurationAdapters,
|
|
): HousekeepingQuery<
|
|
SystemConfigurationQueryInput,
|
|
SystemConfigurationQueryData
|
|
> {
|
|
return {
|
|
id: "system.configuration.query",
|
|
owner: "system",
|
|
capability: configurationCapability,
|
|
async run(context, input) {
|
|
const authorization = authorizeHousekeeping(
|
|
context,
|
|
configurationCapability,
|
|
);
|
|
if (!authorization.ok) return authorization;
|
|
const correlationId = authorization.correlationId;
|
|
|
|
if (input.routeId === "system.configuration.settings") {
|
|
try {
|
|
return ok(
|
|
{
|
|
kind: "settings" as const,
|
|
settings: await adapters.loadWebsiteSettings(),
|
|
partialDependencies: [],
|
|
},
|
|
correlationId,
|
|
);
|
|
} catch {
|
|
return unavailable(correlationId);
|
|
}
|
|
}
|
|
|
|
const [settingsResult, textsResult] = await Promise.allSettled([
|
|
adapters.loadEmulatorSettings(),
|
|
adapters.loadEmulatorTexts(),
|
|
]);
|
|
if (
|
|
settingsResult.status === "rejected" &&
|
|
textsResult.status === "rejected"
|
|
) {
|
|
return unavailable(correlationId);
|
|
}
|
|
|
|
const partialDependencies: string[] = [];
|
|
if (settingsResult.status === "rejected") {
|
|
partialDependencies.push("emulator-settings");
|
|
}
|
|
if (textsResult.status === "rejected") {
|
|
partialDependencies.push("emulator-texts");
|
|
}
|
|
const textData =
|
|
textsResult.status === "fulfilled"
|
|
? textsResult.value
|
|
: { rows: [], total: 0 };
|
|
|
|
return ok(
|
|
{
|
|
kind: "emulator",
|
|
settings:
|
|
settingsResult.status === "fulfilled" ? settingsResult.value : [],
|
|
texts: textData.rows,
|
|
textsTotal: textData.total,
|
|
partialDependencies,
|
|
},
|
|
correlationId,
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
export const systemConfigurationAdapters: SystemConfigurationAdapters = {
|
|
async loadWebsiteSettings() {
|
|
const [{ asc }, { db, WebsiteSetting }] = await Promise.all([
|
|
import("drizzle-orm"),
|
|
import("@/lib/db"),
|
|
]);
|
|
return db
|
|
.select({
|
|
key: WebsiteSetting.key,
|
|
value: WebsiteSetting.value,
|
|
comment: WebsiteSetting.comment,
|
|
})
|
|
.from(WebsiteSetting)
|
|
.orderBy(asc(WebsiteSetting.key));
|
|
},
|
|
async loadEmulatorSettings() {
|
|
const [{ asc }, { db, EmulatorSettings }] = await Promise.all([
|
|
import("drizzle-orm"),
|
|
import("@/lib/db"),
|
|
]);
|
|
return db
|
|
.select({ key: EmulatorSettings.key, value: EmulatorSettings.value })
|
|
.from(EmulatorSettings)
|
|
.orderBy(asc(EmulatorSettings.key));
|
|
},
|
|
async loadEmulatorTexts() {
|
|
const [{ asc, count }, { db, EmulatorTexts }] = await Promise.all([
|
|
import("drizzle-orm"),
|
|
import("@/lib/db"),
|
|
]);
|
|
const [rows, totals] = await Promise.all([
|
|
db
|
|
.select({ key: EmulatorTexts.key, value: EmulatorTexts.value })
|
|
.from(EmulatorTexts)
|
|
.orderBy(asc(EmulatorTexts.key))
|
|
.limit(300),
|
|
db.select({ total: count() }).from(EmulatorTexts),
|
|
]);
|
|
return { rows, total: Number(totals[0]?.total ?? 0) };
|
|
},
|
|
};
|
|
|
|
export const systemConfigurationQuery = createSystemConfigurationQuery(
|
|
systemConfigurationAdapters,
|
|
);
|