@deveye/types 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/domain/device.ts +4 -29
- package/src/domain/featureRegistry.ts +40 -108
- package/src/domain/home.ts +40 -101
- package/src/domain/live.ts +36 -87
- package/src/domain/metrics.ts +10 -14
- package/src/domain/notifications.ts +39 -110
- package/src/domain/project.ts +3 -162
- package/src/domain/report.ts +46 -95
- package/src/domain/secrecy.ts +3 -5
- package/src/domain/sharing.ts +33 -70
- package/src/domain/syncProtocol.ts +5 -12
- package/src/domain/user.ts +8 -14
- package/src/domain/workspace.ts +0 -3
- package/src/domain/workspaceRole.ts +34 -82
- package/src/features/agent.ts +306 -0
- package/src/features/live.ts +17 -37
- package/src/features/notify.ts +19 -57
- package/src/features/registry.ts +7 -44
- package/src/features/secrecy.ts +4 -9
- package/src/features/sharing.ts +12 -26
- package/src/features/user.ts +6 -11
- package/src/features/workspace.ts +6 -11
- package/src/http/auth.ts +6 -13
- package/src/http/device.ts +13 -17
- package/src/http/status.ts +6 -10
- package/src/index.ts +29 -932
- package/src/protocol/agent.ts +40 -70
- package/src/protocol/envelope.ts +3 -10
- package/src/sdk/client-ambient.d.ts +228 -21
- package/src/sdk/client.ts +277 -17
- package/src/sdk/devb.ts +120 -0
- package/src/sdk/manifest.test.ts +0 -1
- package/src/sdk/manifest.ts +94 -25
- package/src/sdk/providers.ts +310 -5
- package/src/sdk/server.ts +483 -19
- package/src/sdk/testing.test.ts +2 -2
- package/src/sdk/testing.ts +302 -26
- package/src/utils/version.ts +5 -8
- package/src/domain/audience.ts +0 -549
- package/src/domain/backup.ts +0 -355
- package/src/domain/credential.ts +0 -55
- package/src/domain/database.ts +0 -467
- package/src/domain/deploy.ts +0 -231
- package/src/domain/finance.ts +0 -477
- package/src/domain/git.ts +0 -419
- package/src/domain/mail.ts +0 -394
- package/src/domain/note.ts +0 -202
- package/src/domain/password.ts +0 -36
- package/src/domain/projectBoard.ts +0 -130
- package/src/domain/projectChat.ts +0 -46
- package/src/domain/projectHistory.ts +0 -82
- package/src/domain/projectLink.ts +0 -87
- package/src/domain/projectPlan.ts +0 -68
- package/src/domain/sentinel.ts +0 -623
- package/src/domain/uptime.ts +0 -216
- package/src/features/audience.ts +0 -275
- package/src/features/backup.ts +0 -230
- package/src/features/database.ts +0 -461
- package/src/features/deploy.ts +0 -245
- package/src/features/device.ts +0 -292
- package/src/features/deviceFiles.ts +0 -83
- package/src/features/deviceLogs.ts +0 -36
- package/src/features/deviceTerminal.ts +0 -57
- package/src/features/finance.ts +0 -360
- package/src/features/git.ts +0 -368
- package/src/features/mail.ts +0 -374
- package/src/features/metrics.ts +0 -185
- package/src/features/note.ts +0 -189
- package/src/features/password.ts +0 -67
- package/src/features/project.ts +0 -709
- package/src/features/sentinel.ts +0 -233
- package/src/features/uptime.ts +0 -186
package/src/domain/mail.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Mail: user-configured IMAP/SMTP mailboxes, read and sent through DevEye.
|
|
5
|
-
*
|
|
6
|
-
* Storage split (see `Docs/SECURITY_MODEL.md`): `securityTier`/`authMethod` and
|
|
7
|
-
* everything the server needs to *plan* a sync or pick a cipher (enabled,
|
|
8
|
-
* timestamps) live in clear columns. Everything identifying — display name,
|
|
9
|
-
* address, credentials, message envelopes — is encrypted, but **which** cipher
|
|
10
|
-
* (`ctx.secure` vs `ctx.secure.open`) is chosen per account by `securityTier`,
|
|
11
|
-
* not fixed per feature like Uptime/Password are. An "open" account can be
|
|
12
|
-
* synced/sent in the background (e.g. wired to Uptime alerts); a "guarded" one
|
|
13
|
-
* only ever decrypts during a live, unlocked session. Message **bodies and
|
|
14
|
-
* attachments are never persisted** — only envelopes (subject/from/date/flags)
|
|
15
|
-
* are cached for fast listing; the body is fetched live from IMAP on open.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export const MAIL_DISPLAY_NAME_MAX_LENGTH = 80;
|
|
19
|
-
export const MAIL_EMAIL_MAX_LENGTH = 320;
|
|
20
|
-
export const MAIL_HOST_MAX_LENGTH = 255;
|
|
21
|
-
export const MAIL_SUBJECT_MAX_LENGTH = 998; // RFC 5322 line-length ceiling
|
|
22
|
-
export const MAIL_SNIPPET_MAX_LENGTH = 280;
|
|
23
|
-
|
|
24
|
-
/** Upper bound on a `mail.messageSearch` query — a needle, not a document. */
|
|
25
|
-
export const MAIL_SEARCH_QUERY_MAX_LENGTH = 128;
|
|
26
|
-
|
|
27
|
-
export const MAIL_SYNC_INTERVAL_MIN_MINUTES = 1;
|
|
28
|
-
export const MAIL_SYNC_INTERVAL_MAX_MINUTES = 180;
|
|
29
|
-
export const MAIL_SYNC_INTERVAL_DEFAULT_MINUTES = 10;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Taille d'une page de messages — et, par voie de conséquence, de la fenêtre que
|
|
33
|
-
* la synchro relit à chaque passage pour y réconcilier drapeaux et disparus.
|
|
34
|
-
*
|
|
35
|
-
* Les deux sont le même nombre à dessein : la relève de fond n'avance pas
|
|
36
|
-
* seulement le haut de la boîte, elle garde honnête exactement ce que l'écran
|
|
37
|
-
* affiche sans défiler. Au-delà, la dérive existe toujours mais ne se voit
|
|
38
|
-
* qu'après un défilement, et se répare au changement de dossier.
|
|
39
|
-
*/
|
|
40
|
-
export const MAIL_MESSAGE_PAGE_SIZE = 50;
|
|
41
|
-
|
|
42
|
-
export const mailSecurityTierSchema = z.enum(['open', 'guarded']);
|
|
43
|
-
export type MailSecurityTier = z.infer<typeof mailSecurityTierSchema>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* État de la dernière opération tentée sur une boîte, quelle qu'en soit
|
|
47
|
-
* l'origine — relève de fond ou commande de l'utilisateur.
|
|
48
|
-
*
|
|
49
|
-
* Trois familles d'échec plutôt qu'un booléen, parce qu'elles n'appellent pas la
|
|
50
|
-
* même chose : `auth` se répare en reconnectant le compte, `unreachable` se
|
|
51
|
-
* répare tout seul quand le réseau revient, `error` demande de lire le message.
|
|
52
|
-
* Le libellé exact reste dans `lastSyncError` — chiffré, lui, car il peut citer
|
|
53
|
-
* un hôte ou une adresse.
|
|
54
|
-
*/
|
|
55
|
-
export const mailAccountStatusSchema = z.enum(['ok', 'auth', 'unreachable', 'error']);
|
|
56
|
-
export type MailAccountStatus = z.infer<typeof mailAccountStatusSchema>;
|
|
57
|
-
|
|
58
|
-
export const mailAuthMethodSchema = z.enum(['password', 'oauth_google', 'oauth_microsoft']);
|
|
59
|
-
export type MailAuthMethod = z.infer<typeof mailAuthMethodSchema>;
|
|
60
|
-
|
|
61
|
-
export const mailOAuthProviderSchema = z.enum(['google', 'microsoft']);
|
|
62
|
-
export type MailOAuthProvider = z.infer<typeof mailOAuthProviderSchema>;
|
|
63
|
-
|
|
64
|
-
export const mailFolderSpecialUseSchema = z.enum([
|
|
65
|
-
'inbox',
|
|
66
|
-
'sent',
|
|
67
|
-
'drafts',
|
|
68
|
-
'trash',
|
|
69
|
-
'junk',
|
|
70
|
-
'archive',
|
|
71
|
-
'other'
|
|
72
|
-
]);
|
|
73
|
-
export type MailFolderSpecialUse = z.infer<typeof mailFolderSpecialUseSchema>;
|
|
74
|
-
|
|
75
|
-
export const mailProxyKindSchema = z.enum(['socks5', 'http']);
|
|
76
|
-
export type MailProxyKind = z.infer<typeof mailProxyKindSchema>;
|
|
77
|
-
|
|
78
|
-
/** Optional manual proxy for a single account — DevEye never provides one itself. */
|
|
79
|
-
export const mailProxySchema = z.object({
|
|
80
|
-
kind: mailProxyKindSchema,
|
|
81
|
-
host: z.string().min(1).max(MAIL_HOST_MAX_LENGTH),
|
|
82
|
-
port: z.number().int().min(1).max(65535),
|
|
83
|
-
username: z.string().max(255).nullable(),
|
|
84
|
-
password: z.string().max(255).nullable()
|
|
85
|
-
});
|
|
86
|
-
export type MailProxy = z.infer<typeof mailProxySchema>;
|
|
87
|
-
|
|
88
|
-
const serverEndpointSchema = z.object({
|
|
89
|
-
host: z.string().min(1).max(MAIL_HOST_MAX_LENGTH),
|
|
90
|
-
port: z.number().int().min(1).max(65535),
|
|
91
|
-
username: z.string().min(1).max(MAIL_EMAIL_MAX_LENGTH),
|
|
92
|
-
password: z.string().min(1)
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* What the user submits to create/edit a **password**-auth account. OAuth
|
|
97
|
-
* accounts are never drafted this way — they're created by the
|
|
98
|
-
* `mail.oauthStart` → provider redirect → callback flow, which never puts a
|
|
99
|
-
* secret through this schema.
|
|
100
|
-
*/
|
|
101
|
-
export const mailAccountDraftSchema = z.object({
|
|
102
|
-
displayName: z.string().min(1).max(MAIL_DISPLAY_NAME_MAX_LENGTH),
|
|
103
|
-
emailAddress: z.string().email().max(MAIL_EMAIL_MAX_LENGTH),
|
|
104
|
-
securityTier: mailSecurityTierSchema,
|
|
105
|
-
imap: serverEndpointSchema,
|
|
106
|
-
smtp: serverEndpointSchema,
|
|
107
|
-
proxy: mailProxySchema.nullable()
|
|
108
|
-
});
|
|
109
|
-
export type MailAccountDraft = z.infer<typeof mailAccountDraftSchema>;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Editing an existing password account, as opposed to creating one. Same shape
|
|
113
|
-
* as a draft with two "leave it alone" affordances the add form has no use for:
|
|
114
|
-
* a blank username/password keeps the stored one, and an omitted `proxy` keeps
|
|
115
|
-
* the stored proxy. Both exist because the account DTO deliberately never
|
|
116
|
-
* echoes a secret back, so the form has nothing to prefill — without them,
|
|
117
|
-
* renaming a mailbox would mean retyping both sets of server credentials, and
|
|
118
|
-
* saving anything at all would silently drop a configured proxy.
|
|
119
|
-
*/
|
|
120
|
-
export const mailAccountEditSchema = mailAccountDraftSchema.extend({
|
|
121
|
-
imap: serverEndpointSchema.extend({
|
|
122
|
-
username: z.string().max(MAIL_EMAIL_MAX_LENGTH),
|
|
123
|
-
password: z.string()
|
|
124
|
-
}),
|
|
125
|
-
smtp: serverEndpointSchema.extend({
|
|
126
|
-
username: z.string().max(MAIL_EMAIL_MAX_LENGTH),
|
|
127
|
-
password: z.string()
|
|
128
|
-
}),
|
|
129
|
-
proxy: mailProxySchema.nullable().optional()
|
|
130
|
-
});
|
|
131
|
-
export type MailAccountEdit = z.infer<typeof mailAccountEditSchema>;
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Client-facing account DTO. Never carries a secret — not the IMAP/SMTP
|
|
135
|
-
* password, not the OAuth tokens, not the proxy credentials. Editing a
|
|
136
|
-
* password-auth account re-submits a full new `MailAccountDraft`; there is no
|
|
137
|
-
* "reveal secret" endpoint, matching Password's discipline of never re-serving
|
|
138
|
-
* a stored secret except through the one gated read path it actually needs.
|
|
139
|
-
*/
|
|
140
|
-
export const mailAccountSchema = z.object({
|
|
141
|
-
id: z.number().int().positive(),
|
|
142
|
-
sortOrder: z.number().int().nonnegative(),
|
|
143
|
-
displayName: z.string(),
|
|
144
|
-
emailAddress: z.string(),
|
|
145
|
-
securityTier: mailSecurityTierSchema,
|
|
146
|
-
authMethod: mailAuthMethodSchema,
|
|
147
|
-
imapHost: z.string(),
|
|
148
|
-
imapPort: z.number().int(),
|
|
149
|
-
smtpHost: z.string(),
|
|
150
|
-
smtpPort: z.number().int(),
|
|
151
|
-
proxyConfigured: z.boolean(),
|
|
152
|
-
enabled: z.boolean(),
|
|
153
|
-
lastSyncAt: z.number().int().nonnegative().nullable(),
|
|
154
|
-
lastSyncError: z.string().nullable(),
|
|
155
|
-
/**
|
|
156
|
-
* État de la dernière opération, persistant jusqu'à ce qu'une réussite le
|
|
157
|
-
* lève. C'est ce qui permet d'annoncer une boîte en panne dès l'ouverture,
|
|
158
|
-
* sans attendre qu'un geste de l'utilisateur reproduise l'échec.
|
|
159
|
-
*/
|
|
160
|
-
status: mailAccountStatusSchema,
|
|
161
|
-
/** Quand l'échec courant a été constaté. `null` tant que tout va bien. */
|
|
162
|
-
lastErrorAt: z.number().int().nonnegative().nullable(),
|
|
163
|
-
/** True when an OAuth refresh failed and the user must reconnect. */
|
|
164
|
-
needsReauth: z.boolean(),
|
|
165
|
-
/**
|
|
166
|
-
* How often the background loop re-checks this mailbox. Per account, not
|
|
167
|
-
* per user: an archive box has no reason to be polled as hard as a work
|
|
168
|
-
* one. Ignored for "guarded" accounts, which are never background-synced.
|
|
169
|
-
* Real precision is floored by the server's own tick
|
|
170
|
-
* (`MAIL_SYNC_TICK_SECONDS`), so a value below that just means "every tick".
|
|
171
|
-
*/
|
|
172
|
-
syncIntervalMinutes: z
|
|
173
|
-
.number()
|
|
174
|
-
.int()
|
|
175
|
-
.min(MAIL_SYNC_INTERVAL_MIN_MINUTES)
|
|
176
|
-
.max(MAIL_SYNC_INTERVAL_MAX_MINUTES),
|
|
177
|
-
/** A background sync tick is currently running for this account (in-memory, never persisted). */
|
|
178
|
-
syncing: z.boolean(),
|
|
179
|
-
/**
|
|
180
|
-
* Folder-level fraction (0-1) of the in-progress sync — the finest
|
|
181
|
-
* granularity available without instrumenting the IMAP fetch itself.
|
|
182
|
-
* `null` while `syncing` means "just started, folder count not known yet";
|
|
183
|
-
* always `null` when not syncing.
|
|
184
|
-
*/
|
|
185
|
-
syncProgress: z.number().min(0).max(1).nullable(),
|
|
186
|
-
created: z.number().int().nonnegative()
|
|
187
|
-
});
|
|
188
|
-
export type MailAccount = z.infer<typeof mailAccountSchema>;
|
|
189
|
-
|
|
190
|
-
export const mailFolderSchema = z.object({
|
|
191
|
-
id: z.number().int().positive(),
|
|
192
|
-
accountId: z.number().int().positive(),
|
|
193
|
-
name: z.string(),
|
|
194
|
-
specialUse: mailFolderSpecialUseSchema,
|
|
195
|
-
sortOrder: z.number().int().nonnegative(),
|
|
196
|
-
unreadCount: z.number().int().nonnegative(),
|
|
197
|
-
totalCount: z.number().int().nonnegative()
|
|
198
|
-
});
|
|
199
|
-
export type MailFolder = z.infer<typeof mailFolderSchema>;
|
|
200
|
-
|
|
201
|
-
export const mailAddressSchema = z.object({
|
|
202
|
-
name: z.string().nullable(),
|
|
203
|
-
address: z.string()
|
|
204
|
-
});
|
|
205
|
-
export type MailAddress = z.infer<typeof mailAddressSchema>;
|
|
206
|
-
|
|
207
|
-
export const mailFlagsSchema = z.object({
|
|
208
|
-
seen: z.boolean(),
|
|
209
|
-
flagged: z.boolean(),
|
|
210
|
-
answered: z.boolean(),
|
|
211
|
-
draft: z.boolean()
|
|
212
|
-
});
|
|
213
|
-
export type MailFlags = z.infer<typeof mailFlagsSchema>;
|
|
214
|
-
|
|
215
|
-
export const mailAttachmentSchema = z.object({
|
|
216
|
-
id: z.string(),
|
|
217
|
-
filename: z.string(),
|
|
218
|
-
mimeType: z.string(),
|
|
219
|
-
size: z.number().int().nonnegative()
|
|
220
|
-
});
|
|
221
|
-
export type MailAttachment = z.infer<typeof mailAttachmentSchema>;
|
|
222
|
-
|
|
223
|
-
/** Why a link was flagged — shown as a warning chip, never blocks the click. */
|
|
224
|
-
export const mailLinkWarningReasonSchema = z.enum([
|
|
225
|
-
'text-href-mismatch',
|
|
226
|
-
'lookalike-domain',
|
|
227
|
-
'unsafe-scheme'
|
|
228
|
-
]);
|
|
229
|
-
export type MailLinkWarningReason = z.infer<typeof mailLinkWarningReasonSchema>;
|
|
230
|
-
|
|
231
|
-
export const mailSuspiciousLinkSchema = z.object({
|
|
232
|
-
text: z.string(),
|
|
233
|
-
href: z.string(),
|
|
234
|
-
reason: mailLinkWarningReasonSchema
|
|
235
|
-
});
|
|
236
|
-
export type MailSuspiciousLink = z.infer<typeof mailSuspiciousLinkSchema>;
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* One header line as the server sent it, in receipt order. Kept raw and
|
|
240
|
-
* unabridged — a `Received:` chain, an SPF/DKIM/DMARC verdict or a mailing-list
|
|
241
|
-
* id is exactly the sort of thing you need when tracing a message, and any
|
|
242
|
-
* summary would be the wrong one for some question.
|
|
243
|
-
*/
|
|
244
|
-
export const mailHeaderSchema = z.object({ name: z.string(), value: z.string() });
|
|
245
|
-
export type MailHeader = z.infer<typeof mailHeaderSchema>;
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Page cursor for `mail.messageList`. Opaque to the client: hand back the one
|
|
249
|
-
* from the previous page.
|
|
250
|
-
*
|
|
251
|
-
* A composite of `(date, id)` rather than a bare row id, because the list is
|
|
252
|
-
* ordered by date and row ids are *insertion* order — which is not the same
|
|
253
|
-
* thing. Backfill and remote search both write older messages into the cache
|
|
254
|
-
* after newer ones, so an id-ordered list put freshly-fetched old mail at the
|
|
255
|
-
* top of the mailbox. `id` is only the tiebreak between two identical dates,
|
|
256
|
-
* so a page boundary can never repeat or skip a row.
|
|
257
|
-
*/
|
|
258
|
-
export const mailMessageCursorSchema = z.object({
|
|
259
|
-
date: z.number().int().nonnegative(),
|
|
260
|
-
id: z.number().int().positive()
|
|
261
|
-
});
|
|
262
|
-
export type MailMessageCursor = z.infer<typeof mailMessageCursorSchema>;
|
|
263
|
-
|
|
264
|
-
/** List-row shape: the cached envelope, nothing that requires a live IMAP fetch. */
|
|
265
|
-
export const mailMessageSummarySchema = z.object({
|
|
266
|
-
id: z.number().int().positive(),
|
|
267
|
-
accountId: z.number().int().positive(),
|
|
268
|
-
folderId: z.number().int().positive(),
|
|
269
|
-
uid: z.number().int().positive(),
|
|
270
|
-
subject: z.string(),
|
|
271
|
-
from: mailAddressSchema.nullable(),
|
|
272
|
-
to: z.array(mailAddressSchema),
|
|
273
|
-
date: z.number().int().nonnegative(),
|
|
274
|
-
flags: mailFlagsSchema,
|
|
275
|
-
hasAttachments: z.boolean(),
|
|
276
|
-
snippet: z.string()
|
|
277
|
-
});
|
|
278
|
-
export type MailMessageSummary = z.infer<typeof mailMessageSummarySchema>;
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Full message. Fetched live from IMAP on `mail.messageGet` — never cached —
|
|
282
|
-
* with `bodyHtml` already sanitized server-side (scripts/handlers/tracking CSS
|
|
283
|
-
* stripped, remote images blocked unless `allowRemoteImages` was requested).
|
|
284
|
-
*/
|
|
285
|
-
export const mailMessageSchema = mailMessageSummarySchema.extend({
|
|
286
|
-
bodyHtml: z.string().nullable(),
|
|
287
|
-
bodyText: z.string().nullable(),
|
|
288
|
-
attachments: z.array(mailAttachmentSchema),
|
|
289
|
-
remoteImagesBlocked: z.boolean(),
|
|
290
|
-
/** Distinct hostnames of the remote images that were blocked, for a "trust these" picker. */
|
|
291
|
-
blockedImageSources: z.array(z.string()),
|
|
292
|
-
suspiciousLinks: z.array(mailSuspiciousLinkSchema),
|
|
293
|
-
/** Every header line as received, in order. Never persisted — read live with the body. */
|
|
294
|
-
headers: z.array(mailHeaderSchema),
|
|
295
|
-
/** Size of the raw RFC822 source, in bytes. */
|
|
296
|
-
sizeBytes: z.number().int().nonnegative(),
|
|
297
|
-
/** IMAP path of the containing mailbox, which with `uid` identifies the message server-side. */
|
|
298
|
-
folderPath: z.string()
|
|
299
|
-
});
|
|
300
|
-
export type MailMessage = z.infer<typeof mailMessageSchema>;
|
|
301
|
-
|
|
302
|
-
export const mailBodyRenderModeSchema = z.enum(['embedded', 'raw']);
|
|
303
|
-
export type MailBodyRenderMode = z.infer<typeof mailBodyRenderModeSchema>;
|
|
304
|
-
|
|
305
|
-
export const mailSettingsSchema = z.object({
|
|
306
|
-
/** Off by default everywhere — opt-in per scope decision, never pushed on the user. */
|
|
307
|
-
externalScanEnabledDefault: z.boolean(),
|
|
308
|
-
/** Hostnames whose remote images auto-load without a per-message prompt. */
|
|
309
|
-
trustedImageDomains: z.array(z.string()),
|
|
310
|
-
/**
|
|
311
|
-
* `embedded` (default): the sanitized body renders inline, styled by DevEye
|
|
312
|
-
* (no `<style>`/inline styles — see `src/mail/sanitize.ts`). `raw`: rendered
|
|
313
|
-
* in an isolated sandboxed iframe on a white background with the message's
|
|
314
|
-
* own styling preserved, for when the embedded look mangles a message.
|
|
315
|
-
*/
|
|
316
|
-
bodyRenderMode: mailBodyRenderModeSchema
|
|
317
|
-
});
|
|
318
|
-
export type MailSettings = z.infer<typeof mailSettingsSchema>;
|
|
319
|
-
|
|
320
|
-
/** Database row shapes (server-only). Mirror the columns exactly. */
|
|
321
|
-
export interface MailAccountRow {
|
|
322
|
-
id: number;
|
|
323
|
-
user_id: number;
|
|
324
|
-
workspace_id: number;
|
|
325
|
-
sort_order: number;
|
|
326
|
-
/** Encrypted (tier-dependent). */
|
|
327
|
-
display_name_enc: string;
|
|
328
|
-
/** Encrypted (tier-dependent). */
|
|
329
|
-
email_address_enc: string;
|
|
330
|
-
security_tier: MailSecurityTier;
|
|
331
|
-
auth_method: MailAuthMethod;
|
|
332
|
-
enabled: number;
|
|
333
|
-
/** Background-sync cadence for this mailbox alone. */
|
|
334
|
-
sync_interval_seconds: number;
|
|
335
|
-
last_sync_at: number | null;
|
|
336
|
-
/** Encrypted (tier-dependent), or null after a clean sync. */
|
|
337
|
-
last_sync_error_enc: string | null;
|
|
338
|
-
/**
|
|
339
|
-
* En clair, à côté de `enabled` et `security_tier` : l'état doit être
|
|
340
|
-
* lisible sans clé — par un ordonnanceur qui trie, par une requête de
|
|
341
|
-
* diagnostic, et par l'interface d'un compte dont le message d'erreur,
|
|
342
|
-
* lui, ne se déchiffre pas.
|
|
343
|
-
*/
|
|
344
|
-
last_sync_status: MailAccountStatus;
|
|
345
|
-
last_error_at: number | null;
|
|
346
|
-
/**
|
|
347
|
-
* Encrypted (tier-dependent) JSON blob — the only place secrets live:
|
|
348
|
-
* `{ imap: {host,port,username,password}, smtp: {...}, proxy? }` for
|
|
349
|
-
* `password` auth, or `{ provider, accessToken, refreshToken, expiresAt,
|
|
350
|
-
* scope }` for OAuth. Never decrypted into the client-facing DTO.
|
|
351
|
-
*/
|
|
352
|
-
credentials_enc: string;
|
|
353
|
-
created: number;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export interface MailFolderRow {
|
|
357
|
-
id: number;
|
|
358
|
-
account_id: number;
|
|
359
|
-
/** Clear: the IMAP mailbox path, required to address the mailbox during sync. */
|
|
360
|
-
imap_path: string;
|
|
361
|
-
/** Encrypted (account's tier) display name. */
|
|
362
|
-
name_enc: string;
|
|
363
|
-
special_use: MailFolderSpecialUse;
|
|
364
|
-
sort_order: number;
|
|
365
|
-
/** IMAP UIDVALIDITY — a change invalidates every cached message in this folder. */
|
|
366
|
-
uid_validity: number | null;
|
|
367
|
-
/** High-water mark: forward (incremental) sync fetches strictly after this. */
|
|
368
|
-
last_seen_uid: number | null;
|
|
369
|
-
/** Low-water mark: backward (backfill) sync fetches strictly before this. NULL until the first sync/backfill sets it. */
|
|
370
|
-
first_seen_uid: number | null;
|
|
371
|
-
unread_count: number;
|
|
372
|
-
total_count: number;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export interface MailMessageRow {
|
|
376
|
-
id: number;
|
|
377
|
-
folder_id: number;
|
|
378
|
-
uid: number;
|
|
379
|
-
/** Encrypted (account's tier) `{ subject, from, to, snippet }`. Body is never stored. */
|
|
380
|
-
envelope_enc: string;
|
|
381
|
-
date: number;
|
|
382
|
-
seen: number;
|
|
383
|
-
flagged: number;
|
|
384
|
-
answered: number;
|
|
385
|
-
has_attachments: number;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export interface MailSettingsRow {
|
|
389
|
-
workspace_id: number;
|
|
390
|
-
external_scan_enabled_default: number;
|
|
391
|
-
/** JSON-encoded string array, or null when empty. Not encrypted — hostnames, not secrets. */
|
|
392
|
-
trusted_image_domains: string | null;
|
|
393
|
-
body_render_mode: MailBodyRenderMode;
|
|
394
|
-
}
|
package/src/domain/note.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A note's body is a modular list of typed blocks rather than free text. This
|
|
5
|
-
* keeps the editor "à la Apple" (one clean surface) while still supporting
|
|
6
|
-
* paragraphs and checklists in any order:
|
|
7
|
-
* - `text` → a paragraph.
|
|
8
|
-
* - `check` → a checklist item, with its own `done` state.
|
|
9
|
-
*
|
|
10
|
-
* - `bullet` → a bulleted (unordered) list item.
|
|
11
|
-
* - `number` → a numbered (ordered) list item; its visible index is derived
|
|
12
|
-
* from its position in the run of consecutive `number` blocks (not stored).
|
|
13
|
-
* - `heading` → a section title, `level` 1 (largest) to 5 (smallest).
|
|
14
|
-
* - `divider` → a horizontal separator; carries no text.
|
|
15
|
-
*
|
|
16
|
-
* Inline emphasis (bold `**`, italic `*`, underline `__`, strikethrough `~~`)
|
|
17
|
-
* is kept as markdown markers inside a block's `text`, not as separate blocks.
|
|
18
|
-
*
|
|
19
|
-
* The shape is intentionally small and forward-compatible: adding a new block
|
|
20
|
-
* kind later (heading, divider, …) only widens this union.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Named colour palette shared by the two colouring features:
|
|
24
|
-
* - inline text colour, carried as `{c:name}…{/c}` markers inside a block's
|
|
25
|
-
* `text` (so it lives in the freeform string, not the schema);
|
|
26
|
-
* - the block-level `color` below, which tints a marker (bullet dot, ordinal,
|
|
27
|
-
* checkbox, divider rule).
|
|
28
|
-
*
|
|
29
|
-
* A *named* palette (not a free hex) keeps the stored value tied to a theme
|
|
30
|
-
* token (`--note-<name>`), so colours stay coherent with the app's design and
|
|
31
|
-
* adapt if the palette is retuned. Widen this enum to add a colour.
|
|
32
|
-
*/
|
|
33
|
-
export const noteColorSchema = z.enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple']);
|
|
34
|
-
|
|
35
|
-
export type NoteColor = z.infer<typeof noteColorSchema>;
|
|
36
|
-
|
|
37
|
-
export const noteTextBlockSchema = z.object({
|
|
38
|
-
type: z.literal('text'),
|
|
39
|
-
text: z.string()
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export const noteCheckBlockSchema = z.object({
|
|
43
|
-
type: z.literal('check'),
|
|
44
|
-
text: z.string(),
|
|
45
|
-
done: z.boolean(),
|
|
46
|
-
/** Optional tint for the checkbox marker (theme token `--note-<color>`). */
|
|
47
|
-
color: noteColorSchema.optional()
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
export const noteBulletBlockSchema = z.object({
|
|
51
|
-
type: z.literal('bullet'),
|
|
52
|
-
text: z.string(),
|
|
53
|
-
/** Optional tint for the bullet dot (theme token `--note-<color>`). */
|
|
54
|
-
color: noteColorSchema.optional()
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
export const noteNumberBlockSchema = z.object({
|
|
58
|
-
type: z.literal('number'),
|
|
59
|
-
text: z.string(),
|
|
60
|
-
/** Optional tint for the ordinal marker (theme token `--note-<color>`). */
|
|
61
|
-
color: noteColorSchema.optional()
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const noteHeadingBlockSchema = z.object({
|
|
65
|
-
type: z.literal('heading'),
|
|
66
|
-
text: z.string(),
|
|
67
|
-
/** Heading level, 1 (largest) to 5 (smallest). */
|
|
68
|
-
level: z.number().int().min(1).max(5)
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export const noteDividerBlockSchema = z.object({
|
|
72
|
-
type: z.literal('divider'),
|
|
73
|
-
/** Optional tint for the horizontal rule (theme token `--note-<color>`). */
|
|
74
|
-
color: noteColorSchema.optional()
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export const noteBlockSchema = z.discriminatedUnion('type', [
|
|
78
|
-
noteTextBlockSchema,
|
|
79
|
-
noteCheckBlockSchema,
|
|
80
|
-
noteBulletBlockSchema,
|
|
81
|
-
noteNumberBlockSchema,
|
|
82
|
-
noteHeadingBlockSchema,
|
|
83
|
-
noteDividerBlockSchema
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
export type NoteTextBlock = z.infer<typeof noteTextBlockSchema>;
|
|
87
|
-
export type NoteCheckBlock = z.infer<typeof noteCheckBlockSchema>;
|
|
88
|
-
export type NoteBulletBlock = z.infer<typeof noteBulletBlockSchema>;
|
|
89
|
-
export type NoteNumberBlock = z.infer<typeof noteNumberBlockSchema>;
|
|
90
|
-
export type NoteHeadingBlock = z.infer<typeof noteHeadingBlockSchema>;
|
|
91
|
-
export type NoteDividerBlock = z.infer<typeof noteDividerBlockSchema>;
|
|
92
|
-
export type NoteBlock = z.infer<typeof noteBlockSchema>;
|
|
93
|
-
|
|
94
|
-
/** Upper bounds, enforced both client- and server-side, to keep rows sane. */
|
|
95
|
-
export const NOTE_TITLE_MAX_LENGTH = 200;
|
|
96
|
-
export const NOTE_FOLDER_MAX_LENGTH = 80;
|
|
97
|
-
export const NOTE_BLOCK_TEXT_MAX_LENGTH = 5_000;
|
|
98
|
-
export const NOTE_MAX_BLOCKS = 500;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* A folder is a first-class, server-persisted bucket. Its `name` is stored
|
|
102
|
-
* encrypted server-side (with the open key, so the folder tree is readable
|
|
103
|
-
* without a password); only the linkage (`folderId` on notes) is kept in clear.
|
|
104
|
-
* `id` is stable across renames, so notes reference it directly.
|
|
105
|
-
*/
|
|
106
|
-
export const noteFolderSchema = z.object({
|
|
107
|
-
id: z.number().int().positive(),
|
|
108
|
-
name: z.string().max(NOTE_FOLDER_MAX_LENGTH),
|
|
109
|
-
/** Rank within the user's folders; lower comes first. Manually reorderable. */
|
|
110
|
-
sortOrder: z.number().int().nonnegative()
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
export type NoteFolder = z.infer<typeof noteFolderSchema>;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* A full note as exchanged with the client. `folderId` references a
|
|
117
|
-
* {@link noteFolderSchema}; `null` means "no folder" (Sans dossier).
|
|
118
|
-
*
|
|
119
|
-
* A **private** note is encrypted with the password-wrapped DEK, so reading or
|
|
120
|
-
* writing it requires the session to be unlocked; a regular note is encrypted
|
|
121
|
-
* with the user's open key, which the server can always resolve — that's what
|
|
122
|
-
* lets the feature open without any prompt.
|
|
123
|
-
*/
|
|
124
|
-
export const noteSchema = z.object({
|
|
125
|
-
id: z.number().int().nonnegative(),
|
|
126
|
-
title: z.string().max(NOTE_TITLE_MAX_LENGTH),
|
|
127
|
-
folderId: z.number().int().positive().nullable(),
|
|
128
|
-
blocks: z.array(noteBlockSchema).max(NOTE_MAX_BLOCKS),
|
|
129
|
-
/** Rank within its folder; lower comes first. Only drag & drop changes it. */
|
|
130
|
-
sortOrder: z.number().int().nonnegative(),
|
|
131
|
-
/** True when the note is encrypted with the password-protected key. */
|
|
132
|
-
private: z.boolean(),
|
|
133
|
-
/** Epoch seconds; set by the server, surfaced for sorting/display. */
|
|
134
|
-
updated: z.number().int().nonnegative(),
|
|
135
|
-
/** Epoch seconds the note was first created. */
|
|
136
|
-
created: z.number().int().nonnegative()
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
export type Note = z.infer<typeof noteSchema>;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Lightweight list variant. A private note listed while the session is locked
|
|
143
|
-
* comes back **masked** — metadata only, no `title`/`preview` — so the UI can
|
|
144
|
-
* render a padlock placeholder without the body ever being decrypted.
|
|
145
|
-
*/
|
|
146
|
-
export const noteSummarySchema = z.object({
|
|
147
|
-
id: z.number().int().nonnegative(),
|
|
148
|
-
title: z.string(),
|
|
149
|
-
folderId: z.number().int().positive().nullable(),
|
|
150
|
-
/** Rank within its folder; lower comes first. Only drag & drop changes it. */
|
|
151
|
-
sortOrder: z.number().int().nonnegative(),
|
|
152
|
-
/** Present only when the body is readable; absent for masked notes. */
|
|
153
|
-
preview: z.string().optional(),
|
|
154
|
-
/** Total checklist items / how many are done — for an at-a-glance summary. */
|
|
155
|
-
checkTotal: z.number().int().nonnegative(),
|
|
156
|
-
checkDone: z.number().int().nonnegative(),
|
|
157
|
-
/** True when the note is encrypted with the password-protected key. */
|
|
158
|
-
private: z.boolean(),
|
|
159
|
-
/** True when the body stayed encrypted for this response (private + locked). */
|
|
160
|
-
masked: z.boolean(),
|
|
161
|
-
/** Epoch seconds the note was archived, or `null` while it is active. */
|
|
162
|
-
archivedAt: z.number().int().nonnegative().nullable(),
|
|
163
|
-
updated: z.number().int().nonnegative(),
|
|
164
|
-
/** Epoch seconds the note was first created. */
|
|
165
|
-
created: z.number().int().nonnegative()
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
export type NoteSummary = z.infer<typeof noteSummarySchema>;
|
|
169
|
-
|
|
170
|
-
export interface NoteRow {
|
|
171
|
-
id: number;
|
|
172
|
-
user_id: number;
|
|
173
|
-
workspace_id: number | null;
|
|
174
|
-
folder_id: number | null;
|
|
175
|
-
/**
|
|
176
|
-
* Encrypted JSON payload (title + blocks), keyed by the private DEK when
|
|
177
|
-
* `is_private`, by the user's open DEK otherwise.
|
|
178
|
-
*/
|
|
179
|
-
content: string;
|
|
180
|
-
/** Manual rank within its folder; lower comes first. */
|
|
181
|
-
sort_order: number;
|
|
182
|
-
/** 1 when the body is encrypted with the password-protected key. */
|
|
183
|
-
is_private: number;
|
|
184
|
-
/**
|
|
185
|
-
* Epoch seconds the note was archived; `NULL` while active. Deleting a note
|
|
186
|
-
* archives it — only an already-archived note can be destroyed for good.
|
|
187
|
-
*/
|
|
188
|
-
archived_at: number | null;
|
|
189
|
-
updated: number;
|
|
190
|
-
created: number;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export interface NoteFolderRow {
|
|
194
|
-
id: number;
|
|
195
|
-
user_id: number;
|
|
196
|
-
workspace_id: number | null;
|
|
197
|
-
/** Encrypted JSON payload (`{ name }`). */
|
|
198
|
-
content: string;
|
|
199
|
-
/** Manual rank within the user's folders; lower comes first. */
|
|
200
|
-
sort_order: number;
|
|
201
|
-
created: number;
|
|
202
|
-
}
|
package/src/domain/password.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
export const passwordStatusSchema = z.enum(['active', 'inactive', 'none']);
|
|
4
|
-
export type PasswordStatus = z.infer<typeof passwordStatusSchema>;
|
|
5
|
-
|
|
6
|
-
export const passwordEntrySchema = z.object({
|
|
7
|
-
id: z.number().int().nonnegative(),
|
|
8
|
-
category: z.string(),
|
|
9
|
-
service: z.string(),
|
|
10
|
-
email: z.string(),
|
|
11
|
-
password: z.string(),
|
|
12
|
-
status: passwordStatusSchema
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export type PasswordEntry = z.infer<typeof passwordEntrySchema>;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Variant without the secret payload, used when listing entries before unlock.
|
|
19
|
-
* `hasPassword` tells the UI whether a (still hidden) secret actually exists, so
|
|
20
|
-
* a genuinely empty password renders as a blank cell instead of fake dots with
|
|
21
|
-
* reveal/copy controls that would yield nothing.
|
|
22
|
-
*/
|
|
23
|
-
export const passwordEntryMaskedSchema = passwordEntrySchema.extend({
|
|
24
|
-
password: z.literal(''),
|
|
25
|
-
hasPassword: z.boolean()
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export type PasswordEntryMasked = z.infer<typeof passwordEntryMaskedSchema>;
|
|
29
|
-
|
|
30
|
-
export interface PasswordRow {
|
|
31
|
-
id: number;
|
|
32
|
-
user_id: number;
|
|
33
|
-
workspace_id: number | null;
|
|
34
|
-
content: string;
|
|
35
|
-
date: number;
|
|
36
|
-
}
|