feat: add reusable mail reference selectors
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
filterSearchableSelectOptions,
|
||||
unavailableReferenceOption,
|
||||
type ApiSettings,
|
||||
type CredentialReferenceSelectorContext,
|
||||
type CredentialReferenceSelectorsUiCapability,
|
||||
type ReferenceOption,
|
||||
type ReferenceOptionProvider
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
fetchMailSettingsDelta,
|
||||
type MailServerProfile
|
||||
} from "../../api/mail";
|
||||
|
||||
export const mailCredentialReferenceSelectors:
|
||||
CredentialReferenceSelectorsUiCapability = {
|
||||
serverProvider: createMailServerReferenceProvider
|
||||
};
|
||||
|
||||
export function createMailServerReferenceProvider(
|
||||
settings: ApiSettings,
|
||||
context: CredentialReferenceSelectorContext
|
||||
): ReferenceOptionProvider {
|
||||
let cataloguePromise: Promise<ReferenceOption[]> | null = null;
|
||||
|
||||
async function catalogue(signal: AbortSignal): Promise<ReferenceOption[]> {
|
||||
cataloguePromise ??= loadProfiles(settings, context, signal)
|
||||
.then(mailServerOptions)
|
||||
.catch((error: unknown) => {
|
||||
cataloguePromise = null;
|
||||
throw error;
|
||||
});
|
||||
const options = await cataloguePromise;
|
||||
if (signal.aborted) throw abortError();
|
||||
return options;
|
||||
}
|
||||
|
||||
return {
|
||||
async search(query, providerContext) {
|
||||
const options = await catalogue(providerContext.signal);
|
||||
return filterSearchableSelectOptions(
|
||||
options,
|
||||
query,
|
||||
providerContext.limit
|
||||
) as ReferenceOption[];
|
||||
},
|
||||
async resolve(values, providerContext) {
|
||||
const options = await catalogue(providerContext.signal);
|
||||
const byValue = new Map(
|
||||
options.map((option) => [option.value, option])
|
||||
);
|
||||
return values.map(
|
||||
(value) =>
|
||||
byValue.get(value)
|
||||
?? unavailableReferenceOption(value, "Unavailable mail server")
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mailServerOptions(
|
||||
profiles: readonly MailServerProfile[]
|
||||
): ReferenceOption[] {
|
||||
return profiles.flatMap((profile) =>
|
||||
(profile.servers ?? []).map((server) => ({
|
||||
value: `mail:${server.id}`,
|
||||
label: server.name,
|
||||
description: [
|
||||
profile.name,
|
||||
server.protocol.toUpperCase(),
|
||||
server.is_active ? null : "Inactive",
|
||||
server.id
|
||||
].filter(Boolean).join(" · "),
|
||||
searchText: [
|
||||
profile.slug,
|
||||
profile.name,
|
||||
server.name,
|
||||
server.protocol,
|
||||
server.id
|
||||
].join(" "),
|
||||
kind: "mail_server",
|
||||
availability: server.is_active ? "available" : "inactive",
|
||||
disabled: !server.is_active,
|
||||
sourceModule: "mail",
|
||||
provenance: {
|
||||
profileId: profile.id,
|
||||
serverId: server.id,
|
||||
protocol: server.protocol,
|
||||
scopeType: server.scope_type,
|
||||
scopeId: server.scope_id
|
||||
}
|
||||
} satisfies ReferenceOption))
|
||||
);
|
||||
}
|
||||
|
||||
async function loadProfiles(
|
||||
settings: ApiSettings,
|
||||
context: CredentialReferenceSelectorContext,
|
||||
signal: AbortSignal
|
||||
): Promise<MailServerProfile[]> {
|
||||
let watermark: string | null = null;
|
||||
let profiles: MailServerProfile[] = [];
|
||||
let first = true;
|
||||
do {
|
||||
const response = await fetchMailSettingsDelta(settings, {
|
||||
scope_type: context.scopeType,
|
||||
scope_id: context.scopeId,
|
||||
include_inactive: true,
|
||||
since: first ? null : watermark,
|
||||
limit: 200
|
||||
});
|
||||
if (signal.aborted) throw abortError();
|
||||
profiles = response.full
|
||||
? response.profiles
|
||||
: mergeProfiles(profiles, response.profiles, response.deleted);
|
||||
watermark = response.watermark ?? null;
|
||||
first = false;
|
||||
if (!response.has_more) break;
|
||||
} while (watermark);
|
||||
return profiles;
|
||||
}
|
||||
|
||||
function mergeProfiles(
|
||||
current: readonly MailServerProfile[],
|
||||
changed: readonly MailServerProfile[],
|
||||
deleted: readonly { resource_type: string; resource_id: string }[]
|
||||
): MailServerProfile[] {
|
||||
const removed = new Set(
|
||||
deleted
|
||||
.filter((item) => item.resource_type === "mail_profile")
|
||||
.map((item) => item.resource_id)
|
||||
);
|
||||
const merged = new Map(
|
||||
current
|
||||
.filter((profile) => !removed.has(profile.id))
|
||||
.map((profile) => [profile.id, profile])
|
||||
);
|
||||
for (const profile of changed) merged.set(profile.id, profile);
|
||||
return [...merged.values()];
|
||||
}
|
||||
|
||||
function abortError(): DOMException {
|
||||
return new DOMException("The operation was aborted.", "AbortError");
|
||||
}
|
||||
Reference in New Issue
Block a user