contacts: Contrack as the robust contacts (C3), picked in Settings or the setup wizard
Spec 11 §4: contacts.provider and contacts.contrack (local address only, company token write-only), each person's own token (users.contrack_token_enc, migration 0010) in My settings, books read from Contrack with a 5-minute cache, Add to contacts and accepted shares created in Contrack, the company account on desk phones. Setup wizard step 8 Contacts. deploy/contrack (2.0.2, pinned), Caddy/nginx blocks, decision 0003, admin guide Contacts. Tested against a fake and a real Contrack 2.0.2 (contrack-live.test.ts).
This commit is contained in:
1 parent
a754974a90
commit
692dd59552
32 files changed
+5787
-120
No files matched your search
@@ -40,9 +40,20 @@ export type ContactData = {
|
||||
};
|
||||
|
||||
export type ContactDto = ContactData & {
|
||||
/** A uuid, or a Contrack contact id such as jane-smith-f60e8536. */
|
||||
id: string;
|
||||
/** mine: I edit it; company: read-only for users (admins edit it in Admin → Contacts). */
|
||||
book: 'mine' | 'company';
|
||||
/** Contrack: the contact's page there (editing happens in Contrack). */
|
||||
url: string | null;
|
||||
};
|
||||
|
||||
/** GET /api/me/contacts and /api/contacts: the book(s), and where they live. */
|
||||
export type ContactBookDto = {
|
||||
items: ContactDto[];
|
||||
provider: 'builtin' | 'contrack';
|
||||
/** Contrack mode: the address people open, and whether this book's token works. */
|
||||
contrack: { publicUrl: string | null; connected: boolean; error: string | null } | null;
|
||||
};
|
||||
|
||||
/** POST …/contacts/import: the files' text, read in the browser. */
|
||||
@@ -66,7 +77,7 @@ export type ContactImportResult = {
|
||||
|
||||
export const ContactShareSchema = z
|
||||
.object({
|
||||
contactIds: z.array(z.uuid()).min(1).max(500),
|
||||
contactIds: z.array(z.string().min(1).max(100)).min(1).max(500),
|
||||
/** Colleagues by extension number. */
|
||||
extensions: z.array(z.string().max(20)).max(500).default([]),
|
||||
everyone: z.boolean().default(false),
|
||||
|
||||
@@ -55,4 +55,6 @@ export type MeDto = {
|
||||
callReportTime: string;
|
||||
callReportWeekday: number;
|
||||
callReportSummaries: boolean;
|
||||
/** Contacts in Contrack mode (11 §4.3): my token, never shown again; null in builtin mode. */
|
||||
contrack: { connected: boolean; tokenPrefix: string | null; publicUrl: string | null } | null;
|
||||
};
|
||||
@@ -104,6 +104,18 @@ export const SettingsSchemas = {
|
||||
'ai.callTranscripts': z.boolean().default(false),
|
||||
/** …and summarized by ai.llm. */
|
||||
'ai.callSummaries': z.boolean().default(false),
|
||||
/** Contacts (spec 11): the builtin books, or Contrack. */
|
||||
'contacts.provider': z.enum(['builtin', 'contrack']).default('builtin'),
|
||||
/** Contrack (11 §4.2): `url` the api calls (local only), `publicUrl` the one people open. */
|
||||
'contacts.contrack': z
|
||||
.object({
|
||||
url: z.string().min(1),
|
||||
publicUrl: z.string().nullable(),
|
||||
/** The company account's personal token (11 §4.3), encrypted; '' = none. */
|
||||
companyTokenEnc: z.string(),
|
||||
})
|
||||
.nullable()
|
||||
.default(null),
|
||||
// Not in 02 §3's table but used by the generator (03 §3).
|
||||
'system.transportHash': z.string().nullable().default(null),
|
||||
};
|
||||
@@ -122,11 +134,26 @@ export const SmtpInputSchema = z.object({
|
||||
});
|
||||
export type SmtpInput = z.input<typeof SmtpInputSchema>;
|
||||
|
||||
/** GET /api/settings: every key, secrets replaced by hasPassword. */
|
||||
/** PATCH /api/settings `contacts.contrack`: the company token is write-only (empty keeps it). */
|
||||
export const ContrackInputSchema = z.object({
|
||||
url: z.url({ protocol: /^https?$/ }).trim(),
|
||||
publicUrl: z
|
||||
.url({ protocol: /^https?$/ })
|
||||
.trim()
|
||||
.nullish()
|
||||
.or(z.literal(''))
|
||||
.transform((s) => s?.replace(/\/+$/, '') || null),
|
||||
companyToken: z.string().trim().max(200).optional(),
|
||||
clearCompanyToken: z.boolean().optional(),
|
||||
});
|
||||
export type ContrackInput = z.input<typeof ContrackInputSchema>;
|
||||
|
||||
/** GET /api/settings: every key, secrets replaced by hasPassword / hasCompanyToken. */
|
||||
export type SettingsDto = {
|
||||
[K in Exclude<SettingKey, 'smtp' | 'sms.webhookToken'>]: SettingValue<K>;
|
||||
[K in Exclude<SettingKey, 'smtp' | 'sms.webhookToken' | 'contacts.contrack'>]: SettingValue<K>;
|
||||
} & {
|
||||
smtp: (Omit<SmtpInput, 'password'> & { hasPassword: boolean }) | null;
|
||||
'contacts.contrack': { url: string; publicUrl: string | null; hasCompanyToken: boolean } | null;
|
||||
};
|
||||
|
||||
/** POST /api/ai/test: one endpoint tried with a tiny request. `audio`: the TTS test, a data: URL. */
|
||||
|
||||
Reference in new issue
Block a user