@deveye/types 0.15.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/LICENSE +21 -0
- package/README.md +23 -0
- package/package.json +68 -0
- package/src/domain/audience.ts +549 -0
- package/src/domain/backup.ts +355 -0
- package/src/domain/credential.ts +55 -0
- package/src/domain/database.ts +467 -0
- package/src/domain/deploy.ts +231 -0
- package/src/domain/device.ts +172 -0
- package/src/domain/deviceFiles.ts +84 -0
- package/src/domain/deviceLogs.ts +82 -0
- package/src/domain/featureRegistry.ts +392 -0
- package/src/domain/finance.ts +477 -0
- package/src/domain/git.ts +419 -0
- package/src/domain/home.ts +314 -0
- package/src/domain/live.ts +272 -0
- package/src/domain/logs.ts +117 -0
- package/src/domain/mail.ts +394 -0
- package/src/domain/metrics.ts +127 -0
- package/src/domain/note.ts +202 -0
- package/src/domain/notifications.ts +268 -0
- package/src/domain/packages.ts +35 -0
- package/src/domain/password.ts +36 -0
- package/src/domain/presence.ts +21 -0
- package/src/domain/project.ts +168 -0
- package/src/domain/projectBoard.ts +130 -0
- package/src/domain/projectChat.ts +46 -0
- package/src/domain/projectHistory.ts +82 -0
- package/src/domain/projectLink.ts +87 -0
- package/src/domain/projectPlan.ts +68 -0
- package/src/domain/report.ts +492 -0
- package/src/domain/role.ts +8 -0
- package/src/domain/secrecy.ts +66 -0
- package/src/domain/sentinel.ts +623 -0
- package/src/domain/sharing.ts +186 -0
- package/src/domain/syncProtocol.ts +116 -0
- package/src/domain/twoFactor.ts +40 -0
- package/src/domain/uptime.ts +216 -0
- package/src/domain/user.ts +141 -0
- package/src/domain/workspace.ts +56 -0
- package/src/domain/workspaceRole.ts +251 -0
- package/src/features/admin.ts +112 -0
- package/src/features/audience.ts +275 -0
- package/src/features/backup.ts +230 -0
- package/src/features/database.ts +461 -0
- package/src/features/deploy.ts +245 -0
- package/src/features/device.ts +292 -0
- package/src/features/deviceFiles.ts +83 -0
- package/src/features/deviceLogs.ts +36 -0
- package/src/features/deviceTerminal.ts +57 -0
- package/src/features/finance.ts +360 -0
- package/src/features/git.ts +368 -0
- package/src/features/home.ts +32 -0
- package/src/features/live.ts +113 -0
- package/src/features/logs.ts +86 -0
- package/src/features/mail.ts +374 -0
- package/src/features/metrics.ts +185 -0
- package/src/features/note.ts +189 -0
- package/src/features/notify.ts +164 -0
- package/src/features/password.ts +67 -0
- package/src/features/project.ts +709 -0
- package/src/features/registry.ts +103 -0
- package/src/features/secrecy.ts +120 -0
- package/src/features/sentinel.ts +233 -0
- package/src/features/sharing.ts +79 -0
- package/src/features/twoFactor.ts +47 -0
- package/src/features/uptime.ts +186 -0
- package/src/features/user.ts +91 -0
- package/src/features/workspace.ts +200 -0
- package/src/http/auth.ts +94 -0
- package/src/http/device.ts +222 -0
- package/src/http/status.ts +45 -0
- package/src/index.ts +1700 -0
- package/src/protocol/agent.ts +1171 -0
- package/src/protocol/envelope.ts +46 -0
- package/src/protocol/error.ts +27 -0
- package/src/protocol/result.ts +17 -0
- package/src/protocol/version.ts +6 -0
- package/src/sdk/client-ambient.d.ts +238 -0
- package/src/sdk/client.ts +66 -0
- package/src/sdk/ids.ts +25 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/manifest.ts +326 -0
- package/src/sdk/providers.ts +48 -0
- package/src/sdk/server.ts +378 -0
- package/src/sdk/testing.ts +179 -0
- package/src/utils/version.ts +28 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
UPTIME_INTERVAL_MAX,
|
|
4
|
+
UPTIME_INTERVAL_MIN,
|
|
5
|
+
UPTIME_KEYWORD_MAX_LENGTH,
|
|
6
|
+
UPTIME_NAME_MAX_LENGTH,
|
|
7
|
+
UPTIME_THRESHOLD_MAX,
|
|
8
|
+
UPTIME_TIMEOUT_MAX,
|
|
9
|
+
UPTIME_TIMEOUT_MIN,
|
|
10
|
+
UPTIME_URL_MAX_LENGTH,
|
|
11
|
+
uptimeCheckSchema,
|
|
12
|
+
uptimeCheckStatsSchema,
|
|
13
|
+
uptimeIncidentSchema,
|
|
14
|
+
uptimeMethodSchema,
|
|
15
|
+
uptimePointSchema,
|
|
16
|
+
uptimeRangeSchema,
|
|
17
|
+
uptimeResolutionSchema,
|
|
18
|
+
uptimeRetentionSchema,
|
|
19
|
+
uptimeServiceSchema
|
|
20
|
+
} from '../domain/uptime';
|
|
21
|
+
|
|
22
|
+
const serviceId = z.number().int().positive();
|
|
23
|
+
|
|
24
|
+
/** Everything the user may set on a service. */
|
|
25
|
+
const uptimeDraftSchema = z.object({
|
|
26
|
+
name: z.string().min(1).max(UPTIME_NAME_MAX_LENGTH),
|
|
27
|
+
url: z.string().url().max(UPTIME_URL_MAX_LENGTH),
|
|
28
|
+
method: uptimeMethodSchema,
|
|
29
|
+
expectedStatus: z.number().int().min(100).max(599).nullable(),
|
|
30
|
+
keyword: z.string().max(UPTIME_KEYWORD_MAX_LENGTH).nullable(),
|
|
31
|
+
intervalSeconds: z.number().int().min(UPTIME_INTERVAL_MIN).max(UPTIME_INTERVAL_MAX),
|
|
32
|
+
timeoutSeconds: z.number().int().min(UPTIME_TIMEOUT_MIN).max(UPTIME_TIMEOUT_MAX),
|
|
33
|
+
failureThreshold: z.number().int().min(1).max(UPTIME_THRESHOLD_MAX),
|
|
34
|
+
retentionDays: uptimeRetentionSchema,
|
|
35
|
+
enabled: z.boolean()
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* List the workspace's services in the user's own order, each carrying its live
|
|
40
|
+
* state and its 24 h / 7 d / 30 d ratios. Never gated: uptime data lives in the
|
|
41
|
+
* open tier, so the feature opens with no password prompt.
|
|
42
|
+
*/
|
|
43
|
+
export const uptimeList = {
|
|
44
|
+
command: 'uptime.list' as const,
|
|
45
|
+
input: z.object({}),
|
|
46
|
+
output: z.object({ services: z.array(uptimeServiceSchema) })
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* "N services up out of M" — the only thing the home card and the navbar widget
|
|
51
|
+
* need. Pure clear metadata, so it costs one indexed count. Paused services are
|
|
52
|
+
* excluded entirely, and `up + down` may be **below** `total`: one awaiting its
|
|
53
|
+
* first probe is neither, and must not be reported as a failure.
|
|
54
|
+
*/
|
|
55
|
+
export const uptimeCount = {
|
|
56
|
+
command: 'uptime.count' as const,
|
|
57
|
+
input: z.object({}),
|
|
58
|
+
output: z.object({
|
|
59
|
+
total: z.number().int().nonnegative(),
|
|
60
|
+
up: z.number().int().nonnegative(),
|
|
61
|
+
down: z.number().int().nonnegative()
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const uptimeAdd = {
|
|
66
|
+
command: 'uptime.add' as const,
|
|
67
|
+
input: z.object({ service: uptimeDraftSchema }),
|
|
68
|
+
output: z.object({ service: uptimeServiceSchema })
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Replace a service's whole configuration. History and incidents are kept. */
|
|
72
|
+
export const uptimeUpdate = {
|
|
73
|
+
command: 'uptime.update' as const,
|
|
74
|
+
input: z.object({ id: serviceId, service: uptimeDraftSchema }),
|
|
75
|
+
output: z.object({ service: uptimeServiceSchema })
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Pause or resume probing without touching the rest of the configuration — a
|
|
80
|
+
* paused service keeps its history and simply stops being scheduled.
|
|
81
|
+
*/
|
|
82
|
+
export const uptimeSetEnabled = {
|
|
83
|
+
command: 'uptime.setEnabled' as const,
|
|
84
|
+
input: z.object({ id: serviceId, enabled: z.boolean() }),
|
|
85
|
+
output: z.object({ service: uptimeServiceSchema })
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Destroy a service **and its whole history** — there is no archive here. */
|
|
89
|
+
export const uptimeRemove = {
|
|
90
|
+
command: 'uptime.remove' as const,
|
|
91
|
+
input: z.object({ id: serviceId }),
|
|
92
|
+
output: z.object({ id: serviceId })
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Lay out the workspace's services: `ids` is the **complete** list in its final
|
|
97
|
+
* order (lower index first). Nothing else positions a service — new ones are
|
|
98
|
+
* appended — so the order is entirely the user's, as it is for notes. Touches no
|
|
99
|
+
* probe state, so reordering never disturbs monitoring.
|
|
100
|
+
*/
|
|
101
|
+
export const uptimeReorder = {
|
|
102
|
+
command: 'uptime.reorder' as const,
|
|
103
|
+
input: z.object({ ids: z.array(serviceId).min(1) }),
|
|
104
|
+
output: z.object({ ids: z.array(serviceId) })
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Probe a service right now instead of waiting for its next tick. */
|
|
108
|
+
export const uptimeCheckNow = {
|
|
109
|
+
command: 'uptime.checkNow' as const,
|
|
110
|
+
input: z.object({ id: serviceId }),
|
|
111
|
+
output: z.object({ service: uptimeServiceSchema })
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Chart series over a window. The server picks the bucket size from the range
|
|
116
|
+
* (`raw` up to 24 h, hourly up to 30 d, daily beyond) and reads the long ranges
|
|
117
|
+
* from the daily rollup, which is never pruned.
|
|
118
|
+
*/
|
|
119
|
+
export const uptimeHistory = {
|
|
120
|
+
command: 'uptime.history' as const,
|
|
121
|
+
input: z.object({ id: serviceId, range: uptimeRangeSchema }),
|
|
122
|
+
output: z.object({
|
|
123
|
+
resolution: uptimeResolutionSchema,
|
|
124
|
+
points: z.array(uptimePointSchema)
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Which pings a journal query is about. Shared by {@link uptimeChecks} and
|
|
130
|
+
* {@link uptimeCheckStats} so a page and its aggregates always describe the
|
|
131
|
+
* exact same selection.
|
|
132
|
+
*/
|
|
133
|
+
const checkFilterSchema = z.object({
|
|
134
|
+
/** Keep only probes at or after this epoch second; `null` = the whole history. */
|
|
135
|
+
since: z.number().int().nonnegative().nullable(),
|
|
136
|
+
/** Restrict to failed probes. */
|
|
137
|
+
failuresOnly: z.boolean()
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Raw ping journal, most recent first. `before` pages backwards through time
|
|
142
|
+
* (exclusive upper bound, epoch seconds).
|
|
143
|
+
*/
|
|
144
|
+
export const uptimeChecks = {
|
|
145
|
+
command: 'uptime.checks' as const,
|
|
146
|
+
input: z.object({
|
|
147
|
+
id: serviceId,
|
|
148
|
+
limit: z.number().int().min(1).max(200),
|
|
149
|
+
before: z.number().int().nonnegative().optional(),
|
|
150
|
+
filter: checkFilterSchema
|
|
151
|
+
}),
|
|
152
|
+
output: z.object({ checks: z.array(uptimeCheckSchema) })
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Aggregates over the same selection {@link uptimeChecks} pages through. Kept a
|
|
157
|
+
* separate command on purpose: it scans the whole filtered range, so the short
|
|
158
|
+
* "last few measures" list on the detail view never pays for it.
|
|
159
|
+
*/
|
|
160
|
+
export const uptimeCheckStats = {
|
|
161
|
+
command: 'uptime.checkStats' as const,
|
|
162
|
+
input: z.object({ id: serviceId, filter: checkFilterSchema }),
|
|
163
|
+
output: z.object({ stats: uptimeCheckStatsSchema })
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Outage history, most recent first; the ongoing one (if any) comes back too. */
|
|
167
|
+
export const uptimeIncidents = {
|
|
168
|
+
command: 'uptime.incidents' as const,
|
|
169
|
+
input: z.object({ id: serviceId, limit: z.number().int().min(1).max(100) }),
|
|
170
|
+
output: z.object({ incidents: z.array(uptimeIncidentSchema) })
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const uptimeCommands = [
|
|
174
|
+
uptimeList,
|
|
175
|
+
uptimeCount,
|
|
176
|
+
uptimeAdd,
|
|
177
|
+
uptimeUpdate,
|
|
178
|
+
uptimeSetEnabled,
|
|
179
|
+
uptimeRemove,
|
|
180
|
+
uptimeReorder,
|
|
181
|
+
uptimeCheckNow,
|
|
182
|
+
uptimeHistory,
|
|
183
|
+
uptimeChecks,
|
|
184
|
+
uptimeCheckStats,
|
|
185
|
+
uptimeIncidents
|
|
186
|
+
] as const;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { userColorSchema, userSettingFlagSchema } from '../domain/user';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Upper bound on the avatar data URL length (characters ≈ bytes for base64
|
|
6
|
+
* ASCII). ~1.5 MB comfortably fits a client-resized JPEG/PNG/WebP thumbnail
|
|
7
|
+
* while bounding the WS payload and the `users.avatar` column.
|
|
8
|
+
*/
|
|
9
|
+
export const AVATAR_MAX_LENGTH = 1_500_000;
|
|
10
|
+
|
|
11
|
+
/** Accepted image data URL prefixes for an uploaded avatar. */
|
|
12
|
+
const avatarDataUrl = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(AVATAR_MAX_LENGTH)
|
|
16
|
+
.regex(
|
|
17
|
+
/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/,
|
|
18
|
+
'Avatar must be a base64 image data URL'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const userSetAvatar = {
|
|
22
|
+
command: 'user.setAvatar' as const,
|
|
23
|
+
input: z.object({ avatar: avatarDataUrl }),
|
|
24
|
+
output: z.object({ avatar: z.string() })
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Max length of a wallpaper data URL stored in the theme (~4 MB in base64). */
|
|
28
|
+
export const THEME_IMAGE_MAX_LENGTH = 4_200_000;
|
|
29
|
+
|
|
30
|
+
/** Number of saved background slots kept in the appearance gallery. */
|
|
31
|
+
export const THEME_SLOT_COUNT = 5;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Upper bound on one saved slot value. Slots hold *compressed* data URLs (the
|
|
35
|
+
* client downscales before saving) or a short raw URL, so this is a safety cap,
|
|
36
|
+
* not the typical size — it keeps the whole theme JSON (5 slots + active image)
|
|
37
|
+
* within the browser's localStorage budget and the `users.theme` column.
|
|
38
|
+
*/
|
|
39
|
+
export const THEME_SLOT_IMAGE_MAX_LENGTH = 1_500_000;
|
|
40
|
+
|
|
41
|
+
export const themeStateSchema = z.object({
|
|
42
|
+
accent: z.string().nullable(),
|
|
43
|
+
bgPreset: z.string().nullable(),
|
|
44
|
+
bgImage: z.string().max(THEME_IMAGE_MAX_LENGTH).nullable(),
|
|
45
|
+
/**
|
|
46
|
+
* Saved background gallery: up to THEME_SLOT_COUNT slots, each a compressed
|
|
47
|
+
* data URL / raw URL, or null for an empty slot. Defaults to empty so themes
|
|
48
|
+
* saved before this field parse cleanly.
|
|
49
|
+
*/
|
|
50
|
+
bgImages: z
|
|
51
|
+
.array(z.string().max(THEME_SLOT_IMAGE_MAX_LENGTH).nullable())
|
|
52
|
+
.max(THEME_SLOT_COUNT)
|
|
53
|
+
.default([]),
|
|
54
|
+
bgDim: z.number().int().min(0).max(100),
|
|
55
|
+
bgBlur: z.number().int().min(0).max(100)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type ThemeStateDTO = z.infer<typeof themeStateSchema>;
|
|
59
|
+
|
|
60
|
+
export const userSetTheme = {
|
|
61
|
+
command: 'user.setTheme' as const,
|
|
62
|
+
input: themeStateSchema,
|
|
63
|
+
output: z.object({ ok: z.literal(true) })
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Couleur d'identité du compte. En `scope: 'account'` côté serveur : elle suit
|
|
68
|
+
* le compte, pas l'espace depuis lequel on la change.
|
|
69
|
+
*/
|
|
70
|
+
export const userSetColor = {
|
|
71
|
+
command: 'user.setColor' as const,
|
|
72
|
+
input: z.object({ color: userColorSchema }),
|
|
73
|
+
output: z.object({ color: userColorSchema })
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Pose ou retire un drapeau de compte. Une commande unique pour tous les
|
|
78
|
+
* drapeaux plutôt qu'une par réglage : le serveur n'a alors qu'un seul chemin de
|
|
79
|
+
* lecture-modification-écriture sur `users.settings`, et un nouveau drapeau ne
|
|
80
|
+
* coûte qu'une entrée dans `userSettingFlagSchema`.
|
|
81
|
+
*
|
|
82
|
+
* La sortie renvoie le sac **complet** tel qu'il vient d'être écrit : le client
|
|
83
|
+
* peut s'aligner dessus au lieu de rejouer sa propre arithmétique.
|
|
84
|
+
*/
|
|
85
|
+
export const userSetSetting = {
|
|
86
|
+
command: 'user.setSetting' as const,
|
|
87
|
+
input: z.object({ flag: userSettingFlagSchema, enabled: z.boolean() }),
|
|
88
|
+
output: z.object({ settings: z.array(z.string()) })
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const userCommands = [userSetAvatar, userSetTheme, userSetColor, userSetSetting] as const;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { homeLayoutSchema } from '../domain/home';
|
|
3
|
+
import { workspaceSchema } from '../domain/workspace';
|
|
4
|
+
import {
|
|
5
|
+
WORKSPACE_ROLE_NAME_MAX,
|
|
6
|
+
workspaceCapabilitySchema,
|
|
7
|
+
workspaceFeatureGrantSchema,
|
|
8
|
+
workspacePermissionsSchema,
|
|
9
|
+
workspaceRoleSchema
|
|
10
|
+
} from '../domain/workspaceRole';
|
|
11
|
+
import { themeStateSchema } from './user';
|
|
12
|
+
|
|
13
|
+
export const workspaceAdd = {
|
|
14
|
+
command: 'workspace.add' as const,
|
|
15
|
+
input: z.object({ name: z.string().min(1).max(120) }),
|
|
16
|
+
output: z.object({ workspace: workspaceSchema })
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const workspaceDelete = {
|
|
20
|
+
command: 'workspace.delete' as const,
|
|
21
|
+
input: z.object({ workspaceId: z.number().int().positive() }),
|
|
22
|
+
output: z.object({ workspaceId: z.number().int().positive() })
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Bascule vers un espace : renvoie son apparence et sa disposition d'accueil.
|
|
27
|
+
*
|
|
28
|
+
* L'espace visé n'apparaît pas dans l'entrée — il voyage sur l'enveloppe, et le
|
|
29
|
+
* dispatcheur a déjà vérifié l'appartenance avant d'appeler le handler. Passer
|
|
30
|
+
* par `/api/auth/me` ne conviendrait pas : cette route recalcule l'espace actif à
|
|
31
|
+
* partir du favori et renverrait l'apparence de l'espace qu'*elle* choisit, pas
|
|
32
|
+
* de celui vers lequel on bascule.
|
|
33
|
+
*/
|
|
34
|
+
export const workspaceActivate = {
|
|
35
|
+
command: 'workspace.activate' as const,
|
|
36
|
+
input: z.object({}),
|
|
37
|
+
output: z.object({
|
|
38
|
+
theme: themeStateSchema.nullable(),
|
|
39
|
+
homeLayout: homeLayoutSchema.nullable(),
|
|
40
|
+
/** Droits dans l'espace rejoint : l'UI s'y conforme dès la bascule. */
|
|
41
|
+
permissions: workspacePermissionsSchema
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Définit (ou efface) l'espace favori du compte : celui chargé en premier à la
|
|
47
|
+
* connexion. `null` → l'espace personnel.
|
|
48
|
+
*/
|
|
49
|
+
export const workspaceSetFavorite = {
|
|
50
|
+
command: 'workspace.setFavorite' as const,
|
|
51
|
+
input: z.object({ workspaceId: z.number().int().positive().nullable() }),
|
|
52
|
+
output: z.object({ workspaceId: z.number().int().positive().nullable() })
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Renomme l'espace actif. Réservé à son propriétaire. */
|
|
56
|
+
export const workspaceRename = {
|
|
57
|
+
command: 'workspace.rename' as const,
|
|
58
|
+
input: z.object({ name: z.string().min(1).max(120) }),
|
|
59
|
+
output: z.object({ workspace: workspaceSchema })
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Quitte l'espace actif. Le propriétaire ne le peut pas : il supprime son espace
|
|
64
|
+
* ou le transmet, il ne l'abandonne pas avec ses membres dedans.
|
|
65
|
+
*/
|
|
66
|
+
export const workspaceLeave = {
|
|
67
|
+
command: 'workspace.leave' as const,
|
|
68
|
+
input: z.object({}),
|
|
69
|
+
output: z.object({ workspaceId: z.number().int().positive() })
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Exclut un membre de l'espace actif. Réservé au propriétaire. */
|
|
73
|
+
export const workspaceRemoveMember = {
|
|
74
|
+
command: 'workspace.removeMember' as const,
|
|
75
|
+
input: z.object({ userId: z.number().int().positive() }),
|
|
76
|
+
output: z.object({ userId: z.number().int().positive() })
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Ajoute un membre à l'espace actif, désigné par son adresse.
|
|
81
|
+
*
|
|
82
|
+
* Pas de lien, pas d'acceptation : le compte existe déjà (l'inscription est sur
|
|
83
|
+
* invitation d'un administrateur), donc le désigner suffit. Un lien serait un
|
|
84
|
+
* détour — et un secret transmissible de plus à surveiller.
|
|
85
|
+
*/
|
|
86
|
+
export const workspaceAddMember = {
|
|
87
|
+
command: 'workspace.addMember' as const,
|
|
88
|
+
input: z.object({ email: z.string().email() }),
|
|
89
|
+
output: z.object({ workspace: workspaceSchema })
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* État de la clé d'espace : déjà active, applicable, et ce qui l'empêcherait.
|
|
94
|
+
*/
|
|
95
|
+
export const workspaceSharedKeyStatus = {
|
|
96
|
+
command: 'workspace.sharedKeyStatus' as const,
|
|
97
|
+
input: z.object({}),
|
|
98
|
+
output: z.object({
|
|
99
|
+
/** L'espace utilise déjà sa propre clé. */
|
|
100
|
+
enabled: z.boolean(),
|
|
101
|
+
/** Faux pour un espace personnel, qui garde la clé de son propriétaire. */
|
|
102
|
+
applicable: z.boolean(),
|
|
103
|
+
/** Obstacles à lever avant conversion, rédigés pour l'utilisateur. */
|
|
104
|
+
blockers: z.array(z.string())
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convertit l'espace vers sa propre clé. Exige le propriétaire, session
|
|
110
|
+
* déverrouillée : le contenu existant doit être relu avec sa clé avant d'être
|
|
111
|
+
* réécrit sous celle de l'espace.
|
|
112
|
+
*/
|
|
113
|
+
export const workspaceEnableSharedKey = {
|
|
114
|
+
command: 'workspace.enableSharedKey' as const,
|
|
115
|
+
input: z.object({}),
|
|
116
|
+
output: z.object({ converted: z.number().int().nonnegative() })
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** Le contenu modifiable d'un rôle. */
|
|
120
|
+
const roleDraftSchema = z.object({
|
|
121
|
+
name: z.string().min(1).max(WORKSPACE_ROLE_NAME_MAX),
|
|
122
|
+
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
123
|
+
capabilities: z.array(workspaceCapabilitySchema),
|
|
124
|
+
features: z.array(workspaceFeatureGrantSchema)
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export const workspaceRoleList = {
|
|
128
|
+
command: 'workspace.roleList' as const,
|
|
129
|
+
input: z.object({}),
|
|
130
|
+
output: z.object({
|
|
131
|
+
roles: z.array(workspaceRoleSchema),
|
|
132
|
+
/** Quel membre porte quel rôle. `null` = aucun, donc aucun droit. */
|
|
133
|
+
memberRoles: z.array(
|
|
134
|
+
z.object({
|
|
135
|
+
userId: z.number().int().positive(),
|
|
136
|
+
roleId: z.number().int().positive().nullable()
|
|
137
|
+
})
|
|
138
|
+
),
|
|
139
|
+
/** Droits effectifs de l'appelant, pour que l'UI n'affiche que l'accessible. */
|
|
140
|
+
permissions: workspacePermissionsSchema
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const workspaceRoleCreate = {
|
|
145
|
+
command: 'workspace.roleCreate' as const,
|
|
146
|
+
input: roleDraftSchema,
|
|
147
|
+
output: z.object({ role: workspaceRoleSchema })
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const workspaceRoleUpdate = {
|
|
151
|
+
command: 'workspace.roleUpdate' as const,
|
|
152
|
+
input: roleDraftSchema.extend({ roleId: z.number().int().positive() }),
|
|
153
|
+
output: z.object({ role: workspaceRoleSchema })
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** Refusé tant que des membres le portent : on ne révoque personne par surprise. */
|
|
157
|
+
export const workspaceRoleDelete = {
|
|
158
|
+
command: 'workspace.roleDelete' as const,
|
|
159
|
+
input: z.object({ roleId: z.number().int().positive() }),
|
|
160
|
+
output: z.object({ roleId: z.number().int().positive() })
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Désigne le rôle attribué d'office à qui rejoint l'espace. */
|
|
164
|
+
export const workspaceRoleSetDefault = {
|
|
165
|
+
command: 'workspace.roleSetDefault' as const,
|
|
166
|
+
input: z.object({ roleId: z.number().int().positive() }),
|
|
167
|
+
output: z.object({ roleId: z.number().int().positive() })
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Attribue un rôle à un membre. `null` le laisse sans aucun droit. */
|
|
171
|
+
export const workspaceAssignRole = {
|
|
172
|
+
command: 'workspace.assignRole' as const,
|
|
173
|
+
input: z.object({
|
|
174
|
+
userId: z.number().int().positive(),
|
|
175
|
+
roleId: z.number().int().positive().nullable()
|
|
176
|
+
}),
|
|
177
|
+
output: z.object({
|
|
178
|
+
userId: z.number().int().positive(),
|
|
179
|
+
roleId: z.number().int().positive().nullable()
|
|
180
|
+
})
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const workspaceCommands = [
|
|
184
|
+
workspaceRoleList,
|
|
185
|
+
workspaceRoleCreate,
|
|
186
|
+
workspaceRoleUpdate,
|
|
187
|
+
workspaceRoleDelete,
|
|
188
|
+
workspaceRoleSetDefault,
|
|
189
|
+
workspaceAssignRole,
|
|
190
|
+
workspaceSharedKeyStatus,
|
|
191
|
+
workspaceEnableSharedKey,
|
|
192
|
+
workspaceRename,
|
|
193
|
+
workspaceLeave,
|
|
194
|
+
workspaceRemoveMember,
|
|
195
|
+
workspaceAddMember,
|
|
196
|
+
workspaceAdd,
|
|
197
|
+
workspaceDelete,
|
|
198
|
+
workspaceActivate,
|
|
199
|
+
workspaceSetFavorite
|
|
200
|
+
] as const;
|
package/src/http/auth.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { homeLayoutSchema } from '../domain/home';
|
|
3
|
+
import { userSchema } from '../domain/user';
|
|
4
|
+
import { workspaceSchema } from '../domain/workspace';
|
|
5
|
+
import { workspacePermissionsSchema } from '../domain/workspaceRole';
|
|
6
|
+
import { themeStateSchema } from '../features/user';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Contenu commun à `/login`, `/refresh` et `/me` : le compte, les espaces
|
|
10
|
+
* auxquels il a accès, et **l'espace actif seul** — son thème et sa disposition
|
|
11
|
+
* d'accueil.
|
|
12
|
+
*
|
|
13
|
+
* Le thème des autres espaces n'est délibérément pas embarqué : `bgImages` peut
|
|
14
|
+
* contenir plusieurs data URLs, et les livrer tous multiplierait la charge utile
|
|
15
|
+
* par le nombre d'espaces. Basculer d'espace va chercher les siens.
|
|
16
|
+
*/
|
|
17
|
+
export const sessionBundleSchema = z.object({
|
|
18
|
+
user: userSchema,
|
|
19
|
+
workspaces: z.array(workspaceSchema),
|
|
20
|
+
/** Espace chargé à l'ouverture : le favori s'il est encore accessible, sinon le personnel. */
|
|
21
|
+
activeWorkspaceId: z.number().int().positive(),
|
|
22
|
+
theme: themeStateSchema.nullable(),
|
|
23
|
+
homeLayout: homeLayoutSchema.nullable(),
|
|
24
|
+
/** Droits de l'appelant dans l'espace actif, pour que l'UI masque le reste. */
|
|
25
|
+
permissions: workspacePermissionsSchema
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type SessionBundle = z.infer<typeof sessionBundleSchema>;
|
|
29
|
+
|
|
30
|
+
export const loginRequestSchema = z.object({
|
|
31
|
+
username: z.string().min(1).max(120),
|
|
32
|
+
password: z.string().min(1).max(512)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type LoginRequest = z.infer<typeof loginRequestSchema>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* L'inscription n'est plus libre : elle exige un jeton d'invitation émis par un
|
|
39
|
+
* administrateur. La route existait déjà, ouverte à tous et connectant
|
|
40
|
+
* automatiquement — simplement inatteignable depuis l'interface, ce qui n'est
|
|
41
|
+
* pas une protection.
|
|
42
|
+
*/
|
|
43
|
+
export const registerRequestSchema = z.object({
|
|
44
|
+
inviteToken: z.string().min(1),
|
|
45
|
+
username: z
|
|
46
|
+
.string()
|
|
47
|
+
.min(3)
|
|
48
|
+
.max(64)
|
|
49
|
+
.regex(/^[a-zA-Z0-9_.-]+$/, 'username may only contain letters, digits, _ . -'),
|
|
50
|
+
email: z.string().email().max(320),
|
|
51
|
+
password: z.string().min(8).max(512)
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type RegisterRequest = z.infer<typeof registerRequestSchema>;
|
|
55
|
+
|
|
56
|
+
export const changePasswordRequestSchema = z.object({
|
|
57
|
+
currentPassword: z.string().min(1).max(512),
|
|
58
|
+
newPassword: z.string().min(8).max(512)
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export type ChangePasswordRequest = z.infer<typeof changePasswordRequestSchema>;
|
|
62
|
+
|
|
63
|
+
export const changePasswordResponseSchema = z.object({ changed: z.literal(true) });
|
|
64
|
+
|
|
65
|
+
export type ChangePasswordResponse = z.infer<typeof changePasswordResponseSchema>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The auth flow returns the authenticated user and its workspaces in one shot.
|
|
69
|
+
* Tokens are delivered as HttpOnly cookies, never in the JSON body.
|
|
70
|
+
*
|
|
71
|
+
* When 2FA is enabled, login first replies with `twoFactorRequired` and an
|
|
72
|
+
* interim challenge token (cookie); the client then posts a TOTP/backup code.
|
|
73
|
+
*/
|
|
74
|
+
export const loginResponseSchema = z.discriminatedUnion('twoFactorRequired', [
|
|
75
|
+
sessionBundleSchema.extend({ twoFactorRequired: z.literal(false) }),
|
|
76
|
+
z.object({ twoFactorRequired: z.literal(true) })
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export type LoginResponse = z.infer<typeof loginResponseSchema>;
|
|
80
|
+
|
|
81
|
+
export const twoFactorChallengeRequestSchema = z.object({
|
|
82
|
+
/** A 6-digit TOTP code or a recovery backup code. */
|
|
83
|
+
code: z.string().min(6).max(16)
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export type TwoFactorChallengeRequest = z.infer<typeof twoFactorChallengeRequestSchema>;
|
|
87
|
+
|
|
88
|
+
export const refreshResponseSchema = sessionBundleSchema;
|
|
89
|
+
|
|
90
|
+
export type RefreshResponse = z.infer<typeof refreshResponseSchema>;
|
|
91
|
+
|
|
92
|
+
export const meResponseSchema = sessionBundleSchema;
|
|
93
|
+
|
|
94
|
+
export type MeResponse = z.infer<typeof meResponseSchema>;
|