@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,141 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { userRoleSchema } from './role';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* État d'un compte au niveau du site. `suspended` conserve toutes les données
|
|
6
|
+
* mais refuse la connexion — c'est une révocation réversible, pas une
|
|
7
|
+
* suppression.
|
|
8
|
+
*/
|
|
9
|
+
export const userStatusSchema = z.enum(['active', 'suspended']);
|
|
10
|
+
export type UserStatus = z.infer<typeof userStatusSchema>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Couleur d'identité du compte : son curseur chez les autres, sa bulle dans la
|
|
14
|
+
* barre du haut, la bordure du nœud où il se trouve. Une identité visuelle, donc
|
|
15
|
+
* portée par le compte et non par l'adhésion à un espace.
|
|
16
|
+
*
|
|
17
|
+
* Huit teintes, le cyan exclu — c'est celui de `--accent`, un curseur de cette
|
|
18
|
+
* couleur se lirait comme un élément de l'interface. La valeur est toujours
|
|
19
|
+
* rendue via le jeton `--user-<nom>` (Styles/theme.css), jamais en hexadécimal.
|
|
20
|
+
*/
|
|
21
|
+
export const userColorSchema = z.enum([
|
|
22
|
+
'red',
|
|
23
|
+
'orange',
|
|
24
|
+
'yellow',
|
|
25
|
+
'green',
|
|
26
|
+
'blue',
|
|
27
|
+
'indigo',
|
|
28
|
+
'purple',
|
|
29
|
+
'pink'
|
|
30
|
+
]);
|
|
31
|
+
export type UserColor = z.infer<typeof userColorSchema>;
|
|
32
|
+
|
|
33
|
+
/** L'ordre fait foi : `defaultUserColor` et la migration 059 l'indexent tous deux. */
|
|
34
|
+
export const USER_COLORS = userColorSchema.options;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Teinte attribuée d'office à un compte, depuis son identifiant. Deux comptes
|
|
38
|
+
* créés à la suite n'ont pas la même, et aucun compte n'existe sans couleur —
|
|
39
|
+
* la migration 059 colorie l'existant, `usersRepo.create` fait de même à
|
|
40
|
+
* l'inscription.
|
|
41
|
+
*/
|
|
42
|
+
export function defaultUserColor(userId: number): UserColor {
|
|
43
|
+
return USER_COLORS[Math.abs(userId) % USER_COLORS.length];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const minimalUserSchema = z.object({
|
|
47
|
+
id: z.number().int().nonnegative(),
|
|
48
|
+
email: z.string().email(),
|
|
49
|
+
username: z.string().min(1),
|
|
50
|
+
avatar: z.string(),
|
|
51
|
+
color: userColorSchema,
|
|
52
|
+
/** Époque Unix de la dernière connexion ; `0` pour un compte jamais venu. */
|
|
53
|
+
lastLogin: z.number().int().nonnegative(),
|
|
54
|
+
created: z.number().int().nonnegative()
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export type MinimalUser = z.infer<typeof minimalUserSchema>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Account security posture, surfaced in the profile as "Sécurité → x / 3".
|
|
61
|
+
* Each flag is one of the three main protections.
|
|
62
|
+
*/
|
|
63
|
+
export const userSecuritySchema = z.object({
|
|
64
|
+
/** TOTP two-factor authentication is enabled. */
|
|
65
|
+
twoFactor: z.boolean(),
|
|
66
|
+
/** Password-based encryption (DEK wrapped by the user's password) is on. */
|
|
67
|
+
passwordEncryption: z.boolean(),
|
|
68
|
+
/**
|
|
69
|
+
* Password re-validation window is configured to a strict value: enabled and
|
|
70
|
+
* short enough (≤ 5 min) to count as a protection. Disabled (0) or a long
|
|
71
|
+
* window does not count.
|
|
72
|
+
*/
|
|
73
|
+
reAuthValidation: z.boolean()
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type UserSecurity = z.infer<typeof userSecuritySchema>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Drapeaux de compte, rangés dans `users.settings` — un simple sac de chaînes.
|
|
80
|
+
*
|
|
81
|
+
* L'**absence** d'un drapeau est sa valeur par défaut : un compte créé avant
|
|
82
|
+
* qu'un drapeau existe se comporte donc comme le reste, sans migration ni
|
|
83
|
+
* rattrapage. C'est ce qui fait de cette colonne le bon endroit pour un réglage
|
|
84
|
+
* booléen privé, là où une colonne dédiée (le modèle de `color`) se justifie
|
|
85
|
+
* quand la valeur n'est pas binaire ou qu'elle intéresse d'autres comptes.
|
|
86
|
+
*
|
|
87
|
+
* - `hideLiveCursors` : ne pas afficher les curseurs des autres membres. La
|
|
88
|
+
* coupure est **réciproque** — le client cesse aussi d'émettre le sien (voir
|
|
89
|
+
* `live/LiveProvider.tsx`), si bien qu'on ne peut pas regarder sans être vu.
|
|
90
|
+
*/
|
|
91
|
+
export const userSettingFlagSchema = z.enum(['hideLiveCursors']);
|
|
92
|
+
export type UserSettingFlag = z.infer<typeof userSettingFlagSchema>;
|
|
93
|
+
|
|
94
|
+
export const userSchema = z.object({
|
|
95
|
+
id: z.number().int().nonnegative(),
|
|
96
|
+
email: z.string().email(),
|
|
97
|
+
username: z.string().min(1),
|
|
98
|
+
avatar: z.string(),
|
|
99
|
+
color: userColorSchema,
|
|
100
|
+
role: userRoleSchema,
|
|
101
|
+
/** Les drapeaux posés — voir `userSettingFlagSchema`. Un drapeau inconnu du
|
|
102
|
+
* client (compte revenu d'une version plus récente) est simplement ignoré,
|
|
103
|
+
* d'où le tableau de chaînes plutôt qu'un tableau d'énumérés. */
|
|
104
|
+
settings: z.array(z.string()),
|
|
105
|
+
security: userSecuritySchema,
|
|
106
|
+
/** Espace personnel du compte : créé avec lui, toujours présent. */
|
|
107
|
+
personalWorkspaceId: z.number().int().positive(),
|
|
108
|
+
/**
|
|
109
|
+
* Espace « favori », chargé en premier à la connexion et au rechargement.
|
|
110
|
+
* `null` → l'espace personnel. Pointe sur un espace dont le compte est
|
|
111
|
+
* membre ; il repasse à `null` si cet espace disparaît ou si l'accès est
|
|
112
|
+
* révoqué.
|
|
113
|
+
*/
|
|
114
|
+
defaultWorkspaceId: z.number().int().positive().nullable(),
|
|
115
|
+
lastLogin: z.number().int().nonnegative(),
|
|
116
|
+
created: z.number().int().nonnegative()
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export type User = z.infer<typeof userSchema>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Database row shape (server-only). Mirrors columns exactly.
|
|
123
|
+
*/
|
|
124
|
+
export interface UserRow {
|
|
125
|
+
id: number;
|
|
126
|
+
email: string;
|
|
127
|
+
username: string;
|
|
128
|
+
password_hash: string;
|
|
129
|
+
avatar: string;
|
|
130
|
+
color: UserColor;
|
|
131
|
+
role: string;
|
|
132
|
+
status: UserStatus;
|
|
133
|
+
settings: string;
|
|
134
|
+
/** Espace personnel du compte (FK `workspaces.id`), unique par utilisateur. */
|
|
135
|
+
personal_workspace_id: number;
|
|
136
|
+
/** Espace favori chargé en premier ; `null` → l'espace personnel. */
|
|
137
|
+
default_workspace_id: number | null;
|
|
138
|
+
re_auth_interval: number | null;
|
|
139
|
+
last_login: number;
|
|
140
|
+
created: number;
|
|
141
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { minimalUserSchema } from './user';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Nature d'un espace.
|
|
6
|
+
*
|
|
7
|
+
* - `personal` : créé avec le compte, un seul membre (son propriétaire), ni
|
|
8
|
+
* quittable ni supprimable, jamais partageable. C'est le repli implicite quand
|
|
9
|
+
* une commande ne vise aucun espace en particulier.
|
|
10
|
+
* - `shared` : créé à la demande, plusieurs membres, rôles et invitations.
|
|
11
|
+
*
|
|
12
|
+
* Les deux sont de vraies lignes de `workspaces` : il n'existe plus d'espace
|
|
13
|
+
* virtuel d'id 0.
|
|
14
|
+
*/
|
|
15
|
+
export const workspaceKindSchema = z.enum(['personal', 'shared']);
|
|
16
|
+
export type WorkspaceKind = z.infer<typeof workspaceKindSchema>;
|
|
17
|
+
|
|
18
|
+
export const workspaceSchema = z.object({
|
|
19
|
+
id: z.number().int().positive(),
|
|
20
|
+
kind: workspaceKindSchema,
|
|
21
|
+
name: z.string().min(1),
|
|
22
|
+
logo: z.string(),
|
|
23
|
+
/** Propriétaire : tous les droits, ne peut être ni exclu ni rétrogradé. */
|
|
24
|
+
ownerUserId: z.number().int().positive(),
|
|
25
|
+
users: z.array(minimalUserSchema),
|
|
26
|
+
features: z.array(z.string().min(1)),
|
|
27
|
+
created: z.number().int().nonnegative()
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type Workspace = z.infer<typeof workspaceSchema>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ligne SQL (serveur uniquement). `theme` et `home_layout` sont portés par
|
|
34
|
+
* l'espace, pas par l'utilisateur : chaque espace a sa propre apparence et sa
|
|
35
|
+
* propre disposition d'accueil.
|
|
36
|
+
*/
|
|
37
|
+
export interface WorkspaceRow {
|
|
38
|
+
id: number;
|
|
39
|
+
kind: WorkspaceKind;
|
|
40
|
+
name: string;
|
|
41
|
+
logo: string;
|
|
42
|
+
owner_user_id: number;
|
|
43
|
+
features: string;
|
|
44
|
+
/** ThemeStateDTO sérialisé, ou null tant qu'aucun thème n'a été enregistré. */
|
|
45
|
+
theme: string | null;
|
|
46
|
+
/** HomeLayout sérialisé, ou null tant qu'aucune disposition n'a été enregistrée. */
|
|
47
|
+
home_layout: string | null;
|
|
48
|
+
created: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WorkspaceMemberRow {
|
|
52
|
+
id: number;
|
|
53
|
+
user_id: number;
|
|
54
|
+
workspace_id: number;
|
|
55
|
+
date: number;
|
|
56
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rôles d'un espace de travail : deux dimensions volontairement distinctes.
|
|
5
|
+
*
|
|
6
|
+
* Les **capacités** portent sur le gouvernement de l'espace lui-même. L'enum est
|
|
7
|
+
* court et fermé : il ne grandit que si la notion d'espace grandit.
|
|
8
|
+
*
|
|
9
|
+
* Les **droits par feature** forment une carte uniforme `feature → lecture ou
|
|
10
|
+
* écriture`. Une nouvelle feature coûte une entrée dans un tableau et hérite du
|
|
11
|
+
* cloisonnement lecture/écriture sans qu'aucun enum ni aucun handler ne change.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const workspaceCapabilitySchema = z.enum([
|
|
15
|
+
/** Renommer l'espace, changer son logo, le supprimer. */
|
|
16
|
+
'workspace.manage',
|
|
17
|
+
/** Ajouter un membre par son adresse, l'exclure, lui attribuer un rôle. */
|
|
18
|
+
'workspace.members',
|
|
19
|
+
/** Créer, modifier, supprimer et ordonner les rôles. */
|
|
20
|
+
'workspace.roles',
|
|
21
|
+
/** Modifier le thème de l'espace. */
|
|
22
|
+
'workspace.appearance',
|
|
23
|
+
/** Modifier la disposition de l'accueil de l'espace. */
|
|
24
|
+
'workspace.layout'
|
|
25
|
+
// « Gérer les canaux d'alerte » a vécu ici (`workspace.notifications`)
|
|
26
|
+
// puis est passée PAR FONCTIONNALITÉ (migration 093) : depuis que chaque
|
|
27
|
+
// émetteur possède ses canaux (091), une capacité d'espace accordait d'un
|
|
28
|
+
// bloc l'astreinte d'Uptime et le salon des sauvegardes. Voir le champ
|
|
29
|
+
// `channels` du grant de feature.
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export type WorkspaceCapability = z.infer<typeof workspaceCapabilitySchema>;
|
|
33
|
+
|
|
34
|
+
export const WORKSPACE_CAPABILITIES = workspaceCapabilitySchema.options;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Features qu'un rôle peut ouvrir. Surensemble de `HomeFeatureId` : `devices`
|
|
38
|
+
* s'y ajoute, parce que voir la flotte d'un espace est un droit comme un autre
|
|
39
|
+
* (lecture = voir les appareils et leur supervision, écriture = les appairer,
|
|
40
|
+
* approuver, renommer, supprimer).
|
|
41
|
+
*
|
|
42
|
+
* `monitoring` n'y figure pas : la carte d'agrégat du même nom est réservée à
|
|
43
|
+
* l'administrateur global dans son espace personnel, donc aucun rôle d'espace
|
|
44
|
+
* ne peut l'accorder. Les vues d'appareil, elles, relèvent de `devices`.
|
|
45
|
+
*/
|
|
46
|
+
export const workspaceFeatureIdSchema = z.enum([
|
|
47
|
+
'devices',
|
|
48
|
+
/**
|
|
49
|
+
* Sentinelle. Distincte de `devices` exprès : voir la supervision d'une
|
|
50
|
+
* machine et voir ce qu'un détecteur soupçonne d'elle ne se confondent pas.
|
|
51
|
+
* `read` = consulter constats et posture, `write` = acquitter, régler les
|
|
52
|
+
* cadences, relancer un relevé.
|
|
53
|
+
*/
|
|
54
|
+
'sentinel',
|
|
55
|
+
'weather',
|
|
56
|
+
'password',
|
|
57
|
+
'notes',
|
|
58
|
+
'cloudsync',
|
|
59
|
+
'uptime',
|
|
60
|
+
'mail',
|
|
61
|
+
'projects',
|
|
62
|
+
'git',
|
|
63
|
+
/**
|
|
64
|
+
* Déploiement. `read` = voir les cibles de l'espace et leur historique,
|
|
65
|
+
* `write` = déclarer une cible, poser la clé d'API de l'instance, et
|
|
66
|
+
* **déclencher une mise en production**.
|
|
67
|
+
*
|
|
68
|
+
* ⚠️ Le droit le plus lourd de conséquences hors de DevEye : c'est le seul
|
|
69
|
+
* qui pousse quelque chose chez un tiers. Distinct de `projects` exprès —
|
|
70
|
+
* piloter le travail et livrer ne se confondent pas, et tout le monde n'a
|
|
71
|
+
* pas à pouvoir faire les deux.
|
|
72
|
+
*/
|
|
73
|
+
'deploy',
|
|
74
|
+
'database',
|
|
75
|
+
/**
|
|
76
|
+
* Sauvegardes. Les destinations de l'espace (dossier serveur, dossier d'une
|
|
77
|
+
* machine enrôlée, bucket S3) et les travaux qui y écrivent.
|
|
78
|
+
*
|
|
79
|
+
* `read` = voir les destinations, les travaux et leur historique, `write` =
|
|
80
|
+
* déclarer une destination, poser sa clé secrète, créer un travail et le
|
|
81
|
+
* déclencher.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ Le droit le plus lourd en lecture après `finance`, et pour une raison
|
|
84
|
+
* différente: la liste des destinations dit **où sont les copies de tout**.
|
|
85
|
+
* Qui la lit sait quel bucket viser pour obtenir la base entière sans jamais
|
|
86
|
+
* toucher à DevEye. Distinct de `database` exprès — superviser une base et
|
|
87
|
+
* savoir où en dorment les vidages ne se confondent pas.
|
|
88
|
+
*/
|
|
89
|
+
'backup',
|
|
90
|
+
/**
|
|
91
|
+
* Finances. Le grand livre de l'espace: comptes, opérations, budgets,
|
|
92
|
+
* échéances.
|
|
93
|
+
*
|
|
94
|
+
* `read` = consulter soldes, journal et tableau de bord, `write` = saisir et
|
|
95
|
+
* corriger des opérations, tenir comptes, catégories, budgets et échéances.
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ Le droit dont la lecture seule est déjà lourde: un livre de comptes dit
|
|
98
|
+
* ce qu'une structure gagne, ce qu'elle doit et à qui elle paie quoi. Le
|
|
99
|
+
* distinguer de `projects` n'est donc pas une commodité de rangement, c'est
|
|
100
|
+
* la raison d'être de la séparation.
|
|
101
|
+
*/
|
|
102
|
+
'finance',
|
|
103
|
+
/**
|
|
104
|
+
* Audience. Le suivi d'usage des sites livrés — dernier maillon de la même
|
|
105
|
+
* famille que `projects`, `git` et `database` : un objet de l'**espace**
|
|
106
|
+
* qu'un projet ne fait que pointer.
|
|
107
|
+
*
|
|
108
|
+
* `read` = consulter les statistiques, `write` = déclarer un site, changer
|
|
109
|
+
* ses origines autorisées, sa rétention, le supprimer.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ Distinct de `projects` exprès, comme `git` l'est déjà : voir les
|
|
112
|
+
* chiffres d'un site livré et piloter le travail qui le produit ne se
|
|
113
|
+
* confondent pas, et tout le monde n'a pas à voir les deux.
|
|
114
|
+
*/
|
|
115
|
+
'audience',
|
|
116
|
+
/**
|
|
117
|
+
* OSINT. `read` = chercher et consulter l'historique de l'espace, `write` =
|
|
118
|
+
* effacer l'historique et poser les clés d'API des fournisseurs.
|
|
119
|
+
*/
|
|
120
|
+
'osint'
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
export type WorkspaceFeatureId = z.infer<typeof workspaceFeatureIdSchema>;
|
|
124
|
+
|
|
125
|
+
export const WORKSPACE_FEATURE_IDS = workspaceFeatureIdSchema.options;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Identifiant d'une feature **externe** (module tiers compilé dans l'app).
|
|
129
|
+
*
|
|
130
|
+
* Le préfixe `x-` porte trois garanties d'un coup : aucune collision possible
|
|
131
|
+
* avec les seize ids natifs ni avec les sujets réservés (`workspace`, `home`,
|
|
132
|
+
* `account`, `notify`, `projectsChat`), aucune confusion avec un UUID
|
|
133
|
+
* d'appareil dans une disposition d'accueil (un UUID commence par un chiffre
|
|
134
|
+
* hexadécimal, jamais par `x`), et un tri visuel immédiat dans un grant ou un
|
|
135
|
+
* journal. Pas de tiret intérieur : l'id sert tel quel de préfixe de commande
|
|
136
|
+
* (`x-crypto.list`) et de valeur de segment live.
|
|
137
|
+
*/
|
|
138
|
+
export const EXTERNAL_FEATURE_ID_PATTERN = /^x-[a-z][a-z0-9]{1,24}$/;
|
|
139
|
+
|
|
140
|
+
export type ExternalFeatureId = `x-${string}`;
|
|
141
|
+
|
|
142
|
+
export const externalFeatureIdSchema = z
|
|
143
|
+
.string()
|
|
144
|
+
.regex(EXTERNAL_FEATURE_ID_PATTERN) as unknown as z.ZodType<ExternalFeatureId>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Toute feature adressable par un droit : native (enum fermé) ou externe.
|
|
148
|
+
*
|
|
149
|
+
* Le surensemble est **pur** : chaque valeur déjà persistée (grants JSON,
|
|
150
|
+
* dispositions) parse inchangée. Une valeur externe inconnue de l'installation
|
|
151
|
+
* courante parse aussi : un rôle peut garder le grant d'un module retiré, il
|
|
152
|
+
* reste simplement inerte tant qu'aucun module ne porte cet id.
|
|
153
|
+
*/
|
|
154
|
+
export const featureIdSchema = z.union([workspaceFeatureIdSchema, externalFeatureIdSchema]);
|
|
155
|
+
|
|
156
|
+
export type FeatureId = WorkspaceFeatureId | ExternalFeatureId;
|
|
157
|
+
|
|
158
|
+
/** Cette valeur est-elle l'id d'une feature externe ? */
|
|
159
|
+
export function isExternalFeatureId(id: string): id is ExternalFeatureId {
|
|
160
|
+
return EXTERNAL_FEATURE_ID_PATTERN.test(id);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** `write` implique `read` : il n'existe pas d'écriture aveugle. */
|
|
164
|
+
export const featureAccessSchema = z.enum(['read', 'write']);
|
|
165
|
+
export type FeatureAccess = z.infer<typeof featureAccessSchema>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Un droit accordé. L'**absence** d'entrée vaut « aucun accès », et la feature
|
|
169
|
+
* disparaît alors de l'interface du membre — une seule règle à retenir, et pas
|
|
170
|
+
* d'état ternaire à normaliser partout.
|
|
171
|
+
*/
|
|
172
|
+
export const workspaceFeatureGrantSchema = z.object({
|
|
173
|
+
feature: featureIdSchema,
|
|
174
|
+
access: featureAccessSchema,
|
|
175
|
+
/**
|
|
176
|
+
* Gérer les **canaux d'alerte** de cette fonctionnalité : en déclarer,
|
|
177
|
+
* corriger une adresse ou une URL, en supprimer, lire leurs destinations.
|
|
178
|
+
*
|
|
179
|
+
* Par fonctionnalité et non par espace (migration 093) : depuis que chaque
|
|
180
|
+
* émetteur possède ses canaux (091), l'adresse de l'astreinte d'Uptime et
|
|
181
|
+
* le salon des sauvegardes ne se confient pas d'un bloc. Distinct de
|
|
182
|
+
* `access` exprès : régler où Uptime écrit relève de `access: write`, et
|
|
183
|
+
* se donne sans livrer les destinations elles-mêmes. Sans effet sur une
|
|
184
|
+
* fonctionnalité qui n'émet pas de notifications.
|
|
185
|
+
*/
|
|
186
|
+
channels: z.boolean(),
|
|
187
|
+
/**
|
|
188
|
+
* Permissions **déclarées par la feature elle-même** (module externe, ou
|
|
189
|
+
* native modernisée) au-delà de lecture/écriture : la clé vient de son
|
|
190
|
+
* manifest (`extraPermissions`), la valeur est un booléen (`toggle`) ou la
|
|
191
|
+
* valeur d'un choix (`choice`).
|
|
192
|
+
*
|
|
193
|
+
* Fermeture par défaut, une seule règle : une clé **absente** vaut « refusé »
|
|
194
|
+
* pour un toggle et « valeur par défaut du manifest » (la moins privilégiée)
|
|
195
|
+
* pour un choix. Le propriétaire, qui a tout, reçoit `true` / la valeur
|
|
196
|
+
* `ownerValue`. Une clé inconnue du manifest courant est rejetée à
|
|
197
|
+
* l'écriture du rôle et ignorée à la lecture ; un grant survivant à la
|
|
198
|
+
* dépose d'un module reste donc inerte, jamais dangereux.
|
|
199
|
+
*/
|
|
200
|
+
extras: z.record(z.string().max(24), z.union([z.boolean(), z.string().max(32)])).default({})
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export type WorkspaceFeatureGrant = z.infer<typeof workspaceFeatureGrantSchema>;
|
|
204
|
+
|
|
205
|
+
export const WORKSPACE_ROLE_NAME_MAX = 64;
|
|
206
|
+
|
|
207
|
+
export const workspaceRoleSchema = z.object({
|
|
208
|
+
id: z.number().int().positive(),
|
|
209
|
+
name: z.string().min(1).max(WORKSPACE_ROLE_NAME_MAX),
|
|
210
|
+
/** Pastille de couleur dans la liste des membres. */
|
|
211
|
+
color: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
212
|
+
/** Ordre d'affichage seul : aucune hiérarchie implicite entre rôles. */
|
|
213
|
+
position: z.number().int().nonnegative(),
|
|
214
|
+
capabilities: z.array(workspaceCapabilitySchema),
|
|
215
|
+
features: z.array(workspaceFeatureGrantSchema),
|
|
216
|
+
/** Attribué d'office à qui rejoint l'espace par invitation. */
|
|
217
|
+
isDefault: z.boolean(),
|
|
218
|
+
memberCount: z.number().int().nonnegative()
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export type WorkspaceRole = z.infer<typeof workspaceRoleSchema>;
|
|
222
|
+
|
|
223
|
+
/** Ligne SQL (serveur uniquement). */
|
|
224
|
+
export interface WorkspaceRoleRow {
|
|
225
|
+
id: number;
|
|
226
|
+
workspace_id: number;
|
|
227
|
+
name: string;
|
|
228
|
+
color: string;
|
|
229
|
+
position: number;
|
|
230
|
+
/** Tableau JSON de `WorkspaceCapability`. */
|
|
231
|
+
capabilities: string;
|
|
232
|
+
/** Tableau JSON de `WorkspaceFeatureGrant`. */
|
|
233
|
+
features: string;
|
|
234
|
+
is_default: number;
|
|
235
|
+
created: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Droits effectifs de l'appelant dans l'espace actif, tels que le client les
|
|
240
|
+
* reçoit pour n'afficher que ce qui est réellement accessible.
|
|
241
|
+
*
|
|
242
|
+
* L'interface s'en sert pour masquer, jamais pour autoriser : chaque commande
|
|
243
|
+
* est de toute façon vérifiée côté serveur.
|
|
244
|
+
*/
|
|
245
|
+
export const workspacePermissionsSchema = z.object({
|
|
246
|
+
isOwner: z.boolean(),
|
|
247
|
+
capabilities: z.array(workspaceCapabilitySchema),
|
|
248
|
+
features: z.array(workspaceFeatureGrantSchema)
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
export type WorkspacePermissions = z.infer<typeof workspacePermissionsSchema>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { userRoleSchema } from '../domain/role';
|
|
3
|
+
import { userStatusSchema } from '../domain/user';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Administration des comptes, à l'échelle du site et non d'un espace.
|
|
7
|
+
*
|
|
8
|
+
* Toutes ces commandes déclarent `admin: true` côté serveur : elles sont
|
|
9
|
+
* réservées au rôle global, seul niveau au-dessus des espaces.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Un compte, tel que la page Utilisateurs l'affiche. */
|
|
13
|
+
export const adminUserSchema = z.object({
|
|
14
|
+
id: z.number().int().positive(),
|
|
15
|
+
username: z.string(),
|
|
16
|
+
email: z.string(),
|
|
17
|
+
avatar: z.string(),
|
|
18
|
+
role: userRoleSchema,
|
|
19
|
+
status: userStatusSchema,
|
|
20
|
+
/** Nombre d'espaces auxquels le compte a accès, personnel compris. */
|
|
21
|
+
workspaceCount: z.number().int().nonnegative(),
|
|
22
|
+
lastLogin: z.number().int().nonnegative(),
|
|
23
|
+
created: z.number().int().nonnegative()
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type AdminUser = z.infer<typeof adminUserSchema>;
|
|
27
|
+
|
|
28
|
+
/** Invitation à créer un compte, en attente d'être consommée. */
|
|
29
|
+
export const adminInviteSchema = z.object({
|
|
30
|
+
token: z.string(),
|
|
31
|
+
url: z.string(),
|
|
32
|
+
/** Adresse sur laquelle l'invitation est verrouillée, ou `null`. */
|
|
33
|
+
email: z.string().nullable(),
|
|
34
|
+
/** Espace rejoint dès la création du compte, ou `null`. */
|
|
35
|
+
workspaceName: z.string().nullable(),
|
|
36
|
+
expiresAt: z.number().int().nullable(),
|
|
37
|
+
maxUses: z.number().int().positive().nullable(),
|
|
38
|
+
uses: z.number().int().nonnegative(),
|
|
39
|
+
createdBy: z.string(),
|
|
40
|
+
created: z.number().int().nonnegative()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export type AdminInvite = z.infer<typeof adminInviteSchema>;
|
|
44
|
+
|
|
45
|
+
export const adminUserList = {
|
|
46
|
+
command: 'admin.userList' as const,
|
|
47
|
+
input: z.object({}),
|
|
48
|
+
output: z.object({ users: z.array(adminUserSchema) })
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const adminSetUserRole = {
|
|
52
|
+
command: 'admin.setUserRole' as const,
|
|
53
|
+
input: z.object({ userId: z.number().int().positive(), role: userRoleSchema }),
|
|
54
|
+
output: z.object({ userId: z.number().int().positive(), role: userRoleSchema })
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Suspendre refuse la connexion sans rien détruire : c'est réversible. */
|
|
58
|
+
export const adminSetUserStatus = {
|
|
59
|
+
command: 'admin.setUserStatus' as const,
|
|
60
|
+
input: z.object({ userId: z.number().int().positive(), status: userStatusSchema }),
|
|
61
|
+
output: z.object({ userId: z.number().int().positive(), status: userStatusSchema })
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Supprime un compte **et tout ce qu'il possède** : son espace personnel, les
|
|
66
|
+
* espaces partagés dont il est propriétaire, et leur contenu, par cascade.
|
|
67
|
+
*/
|
|
68
|
+
export const adminDeleteUser = {
|
|
69
|
+
command: 'admin.deleteUser' as const,
|
|
70
|
+
input: z.object({ userId: z.number().int().positive() }),
|
|
71
|
+
output: z.object({ userId: z.number().int().positive() })
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const adminInviteList = {
|
|
75
|
+
command: 'admin.inviteList' as const,
|
|
76
|
+
input: z.object({}),
|
|
77
|
+
output: z.object({ invites: z.array(adminInviteSchema) })
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const adminInviteCreate = {
|
|
81
|
+
command: 'admin.inviteCreate' as const,
|
|
82
|
+
input: z.object({
|
|
83
|
+
/** Verrouille l'invitation sur une adresse ; vide = ouverte. */
|
|
84
|
+
email: z.string().max(320),
|
|
85
|
+
/** Espace rejoint à la création du compte ; `null` = aucun. */
|
|
86
|
+
workspaceId: z.number().int().positive().nullable(),
|
|
87
|
+
ttlSeconds: z
|
|
88
|
+
.number()
|
|
89
|
+
.int()
|
|
90
|
+
.positive()
|
|
91
|
+
.max(30 * 24 * 3600)
|
|
92
|
+
.nullable(),
|
|
93
|
+
maxUses: z.number().int().positive().max(100).nullable()
|
|
94
|
+
}),
|
|
95
|
+
output: z.object({ invite: adminInviteSchema })
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const adminInviteRevoke = {
|
|
99
|
+
command: 'admin.inviteRevoke' as const,
|
|
100
|
+
input: z.object({ token: z.string().min(1) }),
|
|
101
|
+
output: z.object({ token: z.string().min(1) })
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const adminCommands = [
|
|
105
|
+
adminUserList,
|
|
106
|
+
adminSetUserRole,
|
|
107
|
+
adminSetUserStatus,
|
|
108
|
+
adminDeleteUser,
|
|
109
|
+
adminInviteList,
|
|
110
|
+
adminInviteCreate,
|
|
111
|
+
adminInviteRevoke
|
|
112
|
+
] as const;
|