@imenam/mcp-organizer 0.0.0-stage → 1.0.2
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/README.md +548 -3
- package/dist/shared/logic/first-account.d.ts +13 -0
- package/dist/shared/logic/first-account.js +15 -0
- package/dist/shared/logic/labels.d.ts +5 -0
- package/dist/shared/logic/labels.js +17 -0
- package/dist/shared/logic/positions.d.ts +9 -0
- package/dist/shared/logic/positions.js +19 -0
- package/dist/shared/logic/spaces.d.ts +13 -0
- package/dist/shared/logic/spaces.js +48 -0
- package/dist/shared/logic/test-summary.d.ts +28 -0
- package/dist/shared/logic/test-summary.js +35 -0
- package/dist/shared/schemas/agent-api.schema.d.ts +269 -0
- package/dist/shared/schemas/agent-api.schema.js +97 -0
- package/dist/shared/schemas/agent-tools.schema.d.ts +818 -0
- package/dist/shared/schemas/agent-tools.schema.js +198 -0
- package/dist/shared/schemas/api.schema.d.ts +249 -0
- package/dist/shared/schemas/api.schema.js +116 -0
- package/dist/shared/schemas/entities.schema.d.ts +472 -0
- package/dist/shared/schemas/entities.schema.js +262 -0
- package/dist/shared/schemas/enums.schema.d.ts +135 -0
- package/dist/shared/schemas/enums.schema.js +58 -0
- package/dist/shared/schemas/gui-api.schema.d.ts +53 -0
- package/dist/shared/schemas/gui-api.schema.js +20 -0
- package/dist/shared/schemas/ideas-api.schema.d.ts +81 -0
- package/dist/shared/schemas/ideas-api.schema.js +40 -0
- package/dist/shared/schemas/notifications-api.schema.d.ts +87 -0
- package/dist/shared/schemas/notifications-api.schema.js +58 -0
- package/dist/shared/schemas/tasks-api.schema.d.ts +675 -0
- package/dist/shared/schemas/tasks-api.schema.js +225 -0
- package/dist/shared/schemas/tokens-api.schema.d.ts +106 -0
- package/dist/shared/schemas/tokens-api.schema.js +42 -0
- package/dist/shared/schemas/transcription-api.schema.d.ts +15 -0
- package/dist/shared/schemas/transcription-api.schema.js +12 -0
- package/dist/src/agent-api/checklist.d.ts +3 -0
- package/dist/src/agent-api/checklist.js +127 -0
- package/dist/src/agent-api/ideas.d.ts +3 -0
- package/dist/src/agent-api/ideas.js +93 -0
- package/dist/src/agent-api/notifications.d.ts +16 -0
- package/dist/src/agent-api/notifications.js +91 -0
- package/dist/src/agent-api/requests.d.ts +3 -0
- package/dist/src/agent-api/requests.js +60 -0
- package/dist/src/agent-api/router.d.ts +3 -0
- package/dist/src/agent-api/router.js +41 -0
- package/dist/src/agent-api/tasks.d.ts +2 -0
- package/dist/src/agent-api/tasks.js +290 -0
- package/dist/src/agent-api/types.d.ts +41 -0
- package/dist/src/agent-api/types.js +14 -0
- package/dist/src/cli/backup.d.ts +12 -0
- package/dist/src/cli/backup.js +126 -0
- package/dist/src/cli/prompt.d.ts +7 -0
- package/dist/src/cli/prompt.js +40 -0
- package/dist/src/cli/set-user.d.ts +2 -0
- package/dist/src/cli/set-user.js +114 -0
- package/dist/src/cli/setup-mcp.d.ts +10 -0
- package/dist/src/cli/setup-mcp.js +110 -0
- package/dist/src/cli/setup.d.ts +39 -0
- package/dist/src/cli/setup.js +626 -0
- package/dist/src/cli/watch.d.ts +4 -0
- package/dist/src/cli/watch.js +62 -0
- package/dist/src/core/accounts.d.ts +129 -0
- package/dist/src/core/accounts.js +284 -0
- package/dist/src/core/agent-tokens.d.ts +31 -0
- package/dist/src/core/agent-tokens.js +120 -0
- package/dist/src/core/agent.d.ts +71 -0
- package/dist/src/core/agent.js +358 -0
- package/dist/src/core/attachments.d.ts +21 -0
- package/dist/src/core/attachments.js +44 -0
- package/dist/src/core/data-dir.d.ts +5 -0
- package/dist/src/core/data-dir.js +11 -0
- package/dist/src/core/db.d.ts +13 -0
- package/dist/src/core/db.js +38 -0
- package/dist/src/core/env-file.d.ts +11 -0
- package/dist/src/core/env-file.js +36 -0
- package/dist/src/core/errors.d.ts +24 -0
- package/dist/src/core/errors.js +42 -0
- package/dist/src/core/history.d.ts +34 -0
- package/dist/src/core/history.js +279 -0
- package/dist/src/core/ideas.d.ts +16 -0
- package/dist/src/core/ideas.js +100 -0
- package/dist/src/core/ids.d.ts +2 -0
- package/dist/src/core/ids.js +5 -0
- package/dist/src/core/log-dir.d.ts +2 -0
- package/dist/src/core/log-dir.js +9 -0
- package/dist/src/core/logger.d.ts +14 -0
- package/dist/src/core/logger.js +37 -0
- package/dist/src/core/mail.d.ts +43 -0
- package/dist/src/core/mail.js +44 -0
- package/dist/src/core/mcp-entry.d.ts +20 -0
- package/dist/src/core/mcp-entry.js +15 -0
- package/dist/src/core/migrations.d.ts +18 -0
- package/dist/src/core/migrations.js +404 -0
- package/dist/src/core/native-sqlite.d.ts +2 -0
- package/dist/src/core/native-sqlite.js +23 -0
- package/dist/src/core/notifications.d.ts +46 -0
- package/dist/src/core/notifications.js +173 -0
- package/dist/src/core/project-root.d.ts +2 -0
- package/dist/src/core/project-root.js +13 -0
- package/dist/src/core/requests.d.ts +50 -0
- package/dist/src/core/requests.js +240 -0
- package/dist/src/core/snapshots.d.ts +32 -0
- package/dist/src/core/snapshots.js +35 -0
- package/dist/src/core/spaces.d.ts +66 -0
- package/dist/src/core/spaces.js +252 -0
- package/dist/src/core/store.d.ts +577 -0
- package/dist/src/core/store.js +2119 -0
- package/dist/src/core/task-history.d.ts +7 -0
- package/dist/src/core/task-history.js +41 -0
- package/dist/src/core/tasks.d.ts +58 -0
- package/dist/src/core/tasks.js +389 -0
- package/dist/src/core/test-tasks.d.ts +19 -0
- package/dist/src/core/test-tasks.js +135 -0
- package/dist/src/core/transcription.d.ts +45 -0
- package/dist/src/core/transcription.js +92 -0
- package/dist/src/core/watchers.d.ts +30 -0
- package/dist/src/core/watchers.js +50 -0
- package/dist/src/deploy/env-render.d.ts +26 -0
- package/dist/src/deploy/env-render.js +98 -0
- package/dist/src/deploy/templates.d.ts +38 -0
- package/dist/src/deploy/templates.js +130 -0
- package/dist/src/gui-api.d.ts +37 -0
- package/dist/src/gui-api.js +383 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +110 -0
- package/dist/src/mcp/client.d.ts +38 -0
- package/dist/src/mcp/client.js +106 -0
- package/dist/src/mcp/notifications.d.ts +13 -0
- package/dist/src/mcp/notifications.js +54 -0
- package/dist/src/mcp/start.d.ts +14 -0
- package/dist/src/mcp/start.js +123 -0
- package/dist/src/mcp/tools.d.ts +46 -0
- package/dist/src/mcp/tools.js +348 -0
- package/dist/src/mcp/watch-handoff.d.ts +35 -0
- package/dist/src/mcp/watch-handoff.js +91 -0
- package/dist/src/server/app.d.ts +25 -0
- package/dist/src/server/app.js +305 -0
- package/dist/src/server/config.d.ts +23 -0
- package/dist/src/server/config.js +75 -0
- package/dist/src/server/limits.d.ts +2 -0
- package/dist/src/server/limits.js +4 -0
- package/dist/src/server/login-limiter.d.ts +36 -0
- package/dist/src/server/login-limiter.js +81 -0
- package/dist/src/server/start.d.ts +5 -0
- package/dist/src/server/start.js +88 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +16 -0
- package/gui/dist/assets/index-BVN0agCc.css +1 -0
- package/gui/dist/assets/index-DI1_kr81.js +70 -0
- package/gui/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/gui/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/gui/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/gui/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/gui/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/gui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/gui/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/gui/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/gui/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/gui/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/gui/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/gui/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/gui/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/gui/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/gui/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/gui/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/gui/dist/index.html +13 -0
- package/package.json +66 -4
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { NotFoundError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Notifications (assignation, commentaire) et préférences. Les notifications
|
|
4
|
+
* sont créées par le store dans la transaction de l'événement ; leurs e-mails
|
|
5
|
+
* partent après la validation, et l'issue de chaque envoi est enregistrée.
|
|
6
|
+
*/
|
|
7
|
+
const asUser = (userId) => ({ type: "user", userId });
|
|
8
|
+
/** Longueur maximale de l'extrait d'un commentaire. */
|
|
9
|
+
const EXCERPT_LENGTH = 120;
|
|
10
|
+
/** Nom de l'agent dans les phrases des notifications. */
|
|
11
|
+
const AGENT_NAME = "L'agent";
|
|
12
|
+
function publicBase(publicUrl) {
|
|
13
|
+
return publicUrl?.trim().replace(/\/+$/, "") || null;
|
|
14
|
+
}
|
|
15
|
+
/** Raison pour laquelle les e-mails de notification ne partent pas, ou null s'ils peuvent partir. */
|
|
16
|
+
export function notificationEmailBlocker(deps) {
|
|
17
|
+
if (!deps.mailer.enabled)
|
|
18
|
+
return deps.mailer.disabledReason;
|
|
19
|
+
if (!publicBase(deps.publicUrl)) {
|
|
20
|
+
return "E-mail non envoyé : ORGANIZER_PUBLIC_URL n'est pas défini dans la configuration du site, le lien vers la tâche ne peut pas figurer dans le message.";
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/** Première ligne d'un texte, abrégée. */
|
|
25
|
+
function excerpt(text) {
|
|
26
|
+
const line = text.trim().split(/\r?\n/)[0].trim();
|
|
27
|
+
return line.length > EXCERPT_LENGTH ? `${line.slice(0, EXCERPT_LENGTH - 1)}…` : line;
|
|
28
|
+
}
|
|
29
|
+
function actorName(store, notification) {
|
|
30
|
+
if (notification.actorType === "agent" || notification.actorId === null)
|
|
31
|
+
return null;
|
|
32
|
+
const actor = store.findUser(notification.actorId);
|
|
33
|
+
return actor ? (actor.displayName ?? actor.email) : null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Envoi des e-mails de notification. Abonné au store, il reçoit les
|
|
37
|
+
* notifications dont le destinataire veut un e-mail après la validation de
|
|
38
|
+
* la mutation qui les a créées, envoie chaque e-mail et enregistre son issue
|
|
39
|
+
* (`emailedAt` ou `emailError`).
|
|
40
|
+
*/
|
|
41
|
+
export class NotificationMailer {
|
|
42
|
+
store;
|
|
43
|
+
deps;
|
|
44
|
+
inFlight = new Set();
|
|
45
|
+
constructor(store, deps) {
|
|
46
|
+
this.store = store;
|
|
47
|
+
this.deps = deps;
|
|
48
|
+
}
|
|
49
|
+
/** Abonne l'envoi aux mutations du store. */
|
|
50
|
+
attach() {
|
|
51
|
+
this.store.setEmailListener((ids) => this.enqueue(ids));
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** Attend la fin des envois en cours. */
|
|
55
|
+
async flush() {
|
|
56
|
+
while (this.inFlight.size > 0)
|
|
57
|
+
await Promise.all([...this.inFlight]);
|
|
58
|
+
}
|
|
59
|
+
enqueue(ids) {
|
|
60
|
+
const delivery = this.deliver(ids).finally(() => this.inFlight.delete(delivery));
|
|
61
|
+
this.inFlight.add(delivery);
|
|
62
|
+
}
|
|
63
|
+
async deliver(ids) {
|
|
64
|
+
for (const id of ids) {
|
|
65
|
+
try {
|
|
66
|
+
const notification = this.store.findNotification(id);
|
|
67
|
+
if (!notification)
|
|
68
|
+
continue;
|
|
69
|
+
const outcome = await this.send(notification);
|
|
70
|
+
// La notification disparaît avec sa tâche ou son commentaire, éventuellement supprimés pendant l'envoi.
|
|
71
|
+
if (!this.store.findNotification(id))
|
|
72
|
+
continue;
|
|
73
|
+
this.store.mutate(asUser(notification.userId), (tx) => tx.recordNotificationEmail(id, outcome));
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
this.deps.logError(`E-mail of notification ${id} failed: ${err?.stack ?? err}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async send(notification) {
|
|
81
|
+
const blocker = notificationEmailBlocker(this.deps);
|
|
82
|
+
if (blocker)
|
|
83
|
+
return { sent: false, reason: blocker };
|
|
84
|
+
const recipient = this.store.getUser(notification.userId);
|
|
85
|
+
const outcome = await this.deps.mailer.send(this.message(notification, recipient.email));
|
|
86
|
+
return outcome.sent ? { sent: true } : { sent: false, reason: outcome.reason };
|
|
87
|
+
}
|
|
88
|
+
message(notification, to) {
|
|
89
|
+
const task = this.store.getTask(notification.taskId);
|
|
90
|
+
const space = this.store.getSpace(task.spaceId);
|
|
91
|
+
const key = `${space.keyPrefix}-${task.number}`;
|
|
92
|
+
const who = actorName(this.store, notification) ?? AGENT_NAME;
|
|
93
|
+
const url = `${publicBase(this.deps.publicUrl)}/#/task/${encodeURIComponent(task.id)}`;
|
|
94
|
+
const footer = `\n\nOuvrir la tâche : ${url}\n\nCes e-mails se désactivent dans les Paramètres de mcp-organizer.\n`;
|
|
95
|
+
if (notification.type === "assigned") {
|
|
96
|
+
return {
|
|
97
|
+
to,
|
|
98
|
+
subject: `${who} vous a assigné « ${task.title} »`,
|
|
99
|
+
text: `${who} vous a assigné la tâche ${key} « ${task.title} » dans l'espace « ${space.name} ».${footer}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const comment = notification.commentId === null ? null : this.store.findComment(notification.commentId);
|
|
103
|
+
return {
|
|
104
|
+
to,
|
|
105
|
+
subject: `${who} a commenté « ${task.title} »`,
|
|
106
|
+
text: `${who} a commenté la tâche ${key} « ${task.title} » dans l'espace « ${space.name} » :\n\n${comment?.body ?? ""}${footer}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// --- API ------------------------------------------------------------------------
|
|
111
|
+
function notificationView(store, notification) {
|
|
112
|
+
const task = store.getTask(notification.taskId);
|
|
113
|
+
const space = store.getSpace(task.spaceId);
|
|
114
|
+
const comment = notification.commentId === null ? null : store.findComment(notification.commentId);
|
|
115
|
+
return {
|
|
116
|
+
id: notification.id,
|
|
117
|
+
type: notification.type,
|
|
118
|
+
taskId: task.id,
|
|
119
|
+
taskKey: `${space.keyPrefix}-${task.number}`,
|
|
120
|
+
taskTitle: task.title,
|
|
121
|
+
actorType: notification.actorType,
|
|
122
|
+
actorId: notification.actorId,
|
|
123
|
+
actorName: actorName(store, notification),
|
|
124
|
+
commentExcerpt: comment ? excerpt(comment.body) : null,
|
|
125
|
+
createdAt: notification.createdAt,
|
|
126
|
+
readAt: notification.readAt,
|
|
127
|
+
emailedAt: notification.emailedAt,
|
|
128
|
+
emailError: notification.emailError,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Notifications de l'utilisateur, non lues d'abord, par page. */
|
|
132
|
+
export function listNotifications(store, userId, query) {
|
|
133
|
+
const page = store.listNotifications(userId, query.limit + 1, query.offset);
|
|
134
|
+
return {
|
|
135
|
+
notifications: page.slice(0, query.limit).map((notification) => notificationView(store, notification)),
|
|
136
|
+
unreadCount: store.countUnreadNotifications(userId),
|
|
137
|
+
hasMore: page.length > query.limit,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function unreadCount(store, userId) {
|
|
141
|
+
return { unreadCount: store.countUnreadNotifications(userId) };
|
|
142
|
+
}
|
|
143
|
+
/** Marque lue une notification de l'utilisateur ; celle d'un autre est introuvable. */
|
|
144
|
+
export function markNotificationRead(store, userId, notificationId) {
|
|
145
|
+
const notification = store.findNotification(notificationId);
|
|
146
|
+
if (!notification || notification.userId !== userId || !notification.inApp)
|
|
147
|
+
throw new NotFoundError("Notification introuvable.");
|
|
148
|
+
store.mutate(asUser(userId), (tx) => tx.markNotificationRead(notificationId));
|
|
149
|
+
return unreadCount(store, userId);
|
|
150
|
+
}
|
|
151
|
+
export function markAllNotificationsRead(store, userId) {
|
|
152
|
+
store.mutate(asUser(userId), (tx) => tx.markAllNotificationsRead());
|
|
153
|
+
return unreadCount(store, userId);
|
|
154
|
+
}
|
|
155
|
+
function settingsView(store, deps, userId) {
|
|
156
|
+
const { notifyAssignApp, notifyAssignEmail, notifyCommentApp, notifyCommentEmail } = store.getUserSettings(userId);
|
|
157
|
+
const reason = notificationEmailBlocker(deps);
|
|
158
|
+
return {
|
|
159
|
+
notifyAssignApp,
|
|
160
|
+
notifyAssignEmail,
|
|
161
|
+
notifyCommentApp,
|
|
162
|
+
notifyCommentEmail,
|
|
163
|
+
email: { enabled: reason === null, reason },
|
|
164
|
+
transcription: { enabled: deps.transcriber.enabled, reason: deps.transcriber.disabledReason },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export function getSettings(store, deps, userId) {
|
|
168
|
+
return settingsView(store, deps, userId);
|
|
169
|
+
}
|
|
170
|
+
export function updateSettings(store, deps, userId, patch) {
|
|
171
|
+
store.mutate(asUser(userId), (tx) => tx.updateUserSettings(userId, patch));
|
|
172
|
+
return settingsView(store, deps, userId);
|
|
173
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Racine du package : premier dossier contenant un package.json en remontant depuis `startDir`. */
|
|
4
|
+
export function findProjectRoot(startDir) {
|
|
5
|
+
let current = startDir;
|
|
6
|
+
while (!fs.existsSync(path.join(current, "package.json"))) {
|
|
7
|
+
const parent = path.dirname(current);
|
|
8
|
+
if (parent === current)
|
|
9
|
+
throw new Error(`No package.json found above ${startDir}: the mcp-organizer package is incomplete, reinstall it.`);
|
|
10
|
+
current = parent;
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
import type { AgentAction, AgentRequest } from "../../shared/schemas/entities.schema.js";
|
|
3
|
+
import type { RequestKind } from "../../shared/schemas/enums.schema.js";
|
|
4
|
+
import type { AgentActionView, RequestListResponse, RequestView } from "../../shared/schemas/agent-api.schema.js";
|
|
5
|
+
/**
|
|
6
|
+
* File de l'agent vue de la GUI : dépôt d'une demande, modification et
|
|
7
|
+
* annulation tant qu'elle attend, libération d'une demande prise, liste par
|
|
8
|
+
* espace, annulation des actions de l'agent.
|
|
9
|
+
*
|
|
10
|
+
* Une demande vise l'espace où elle est écrite. Droits : les membres de cet
|
|
11
|
+
* espace la voient, la modifient et l'annulent tant qu'elle est en attente,
|
|
12
|
+
* la libèrent quand l'agent l'a prise, voient et annulent les actions de son
|
|
13
|
+
* lot. Les droits sont vérifiés dans la transaction de l'écriture.
|
|
14
|
+
*/
|
|
15
|
+
/** Nombre maximal de demandes renvoyées pour un espace. */
|
|
16
|
+
export declare const REQUESTS_PAGE_SIZE = 200;
|
|
17
|
+
export declare function actionView(store: Store, action: AgentAction): AgentActionView;
|
|
18
|
+
export declare function requestView(store: Store, userId: string, request: AgentRequest): RequestView;
|
|
19
|
+
/** Entrée d'une demande déposée par la GUI ou par une review de test. */
|
|
20
|
+
export interface NewRequestInput {
|
|
21
|
+
kind: RequestKind;
|
|
22
|
+
text: string;
|
|
23
|
+
contextTaskId?: string | null;
|
|
24
|
+
contextIdeaId?: string | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Dépose une demande de l'utilisateur, écrite dans l'espace `spaceId`, qui
|
|
28
|
+
* est aussi l'espace où l'agent agira. La tâche de contexte appartient à cet
|
|
29
|
+
* espace ; une promotion d'idée porte une idée de cet espace.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createRequest(store: Store, userId: string, spaceId: string, input: NewRequestInput): RequestView;
|
|
32
|
+
export declare function updateRequest(store: Store, userId: string, requestId: string, text: string): RequestView;
|
|
33
|
+
export declare function cancelRequest(store: Store, userId: string, requestId: string): RequestView;
|
|
34
|
+
/**
|
|
35
|
+
* Libère une demande que l'agent a prise sans la terminer : elle redevient
|
|
36
|
+
* `pending` et un agent pourra la reprendre. Les actions déjà faites restent
|
|
37
|
+
* dans son lot. Réservé aux membres de l'espace cible.
|
|
38
|
+
*/
|
|
39
|
+
export declare function releaseRequest(store: Store, userId: string, requestId: string): RequestView;
|
|
40
|
+
export declare function getRequest(store: Store, userId: string, requestId: string): RequestView;
|
|
41
|
+
/**
|
|
42
|
+
* Demandes d'un espace, avec le nombre de demandes en attente et les jetons
|
|
43
|
+
* actifs qui couvrent l'espace, chacun avec son dernier usage et la présence
|
|
44
|
+
* de son veilleur de notifications.
|
|
45
|
+
*/
|
|
46
|
+
export declare function listRequests(store: Store, userId: string, spaceId: string, now?: Date): RequestListResponse;
|
|
47
|
+
/** Annule une action de l'agent. */
|
|
48
|
+
export declare function undoAction(store: Store, userId: string, actionId: string): RequestView;
|
|
49
|
+
/** Annule toutes les actions non annulées d'un lot, de la plus récente à la plus ancienne : tout le lot ou rien. */
|
|
50
|
+
export declare function undoRequest(store: Store, userId: string, requestId: string): RequestView;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { InvariantError, NotFoundError, OrganizerError } from "./errors.js";
|
|
2
|
+
import { requireMember } from "./spaces.js";
|
|
3
|
+
import { requireTask } from "./tasks.js";
|
|
4
|
+
import { undoAgentAction } from "./history.js";
|
|
5
|
+
import { taskIdOfAction } from "./snapshots.js";
|
|
6
|
+
import { tokenState } from "./agent-tokens.js";
|
|
7
|
+
/**
|
|
8
|
+
* File de l'agent vue de la GUI : dépôt d'une demande, modification et
|
|
9
|
+
* annulation tant qu'elle attend, libération d'une demande prise, liste par
|
|
10
|
+
* espace, annulation des actions de l'agent.
|
|
11
|
+
*
|
|
12
|
+
* Une demande vise l'espace où elle est écrite. Droits : les membres de cet
|
|
13
|
+
* espace la voient, la modifient et l'annulent tant qu'elle est en attente,
|
|
14
|
+
* la libèrent quand l'agent l'a prise, voient et annulent les actions de son
|
|
15
|
+
* lot. Les droits sont vérifiés dans la transaction de l'écriture.
|
|
16
|
+
*/
|
|
17
|
+
/** Nombre maximal de demandes renvoyées pour un espace. */
|
|
18
|
+
export const REQUESTS_PAGE_SIZE = 200;
|
|
19
|
+
const asUser = (userId) => ({ type: "user", userId });
|
|
20
|
+
function userName(store, userId) {
|
|
21
|
+
if (userId === null)
|
|
22
|
+
return null;
|
|
23
|
+
const user = store.findUser(userId);
|
|
24
|
+
return user ? (user.displayName ?? user.email) : null;
|
|
25
|
+
}
|
|
26
|
+
// --- Vues -----------------------------------------------------------------------
|
|
27
|
+
/** Jeton d'agent désigné par son nom et celui de son propriétaire. */
|
|
28
|
+
function agentRef(store, token) {
|
|
29
|
+
return { tokenId: token.id, name: token.name, ownerName: userName(store, token.userId) ?? "" };
|
|
30
|
+
}
|
|
31
|
+
/** Libellé de la tâche concernée par une action : `ORG-41`, « Idée » pour une idée. */
|
|
32
|
+
function actionLabel(store, action) {
|
|
33
|
+
if (action.entityType === "idea")
|
|
34
|
+
return "Idée";
|
|
35
|
+
if (action.entityType === "task") {
|
|
36
|
+
const snapshot = (action.after ?? action.before);
|
|
37
|
+
const space = store.findSpace(snapshot.spaceId);
|
|
38
|
+
return space ? `${space.keyPrefix}-${snapshot.number}` : "";
|
|
39
|
+
}
|
|
40
|
+
const taskId = taskIdOfAction(action);
|
|
41
|
+
const task = taskId === null ? null : store.findTask(taskId);
|
|
42
|
+
return task ? `${store.getSpace(task.spaceId).keyPrefix}-${task.number}` : "";
|
|
43
|
+
}
|
|
44
|
+
export function actionView(store, action) {
|
|
45
|
+
const taskId = taskIdOfAction(action);
|
|
46
|
+
return {
|
|
47
|
+
id: action.id,
|
|
48
|
+
seq: action.seq,
|
|
49
|
+
entityType: action.entityType,
|
|
50
|
+
operation: action.operation,
|
|
51
|
+
label: actionLabel(store, action),
|
|
52
|
+
summary: action.summary,
|
|
53
|
+
taskId: taskId !== null && store.findTask(taskId) ? taskId : null,
|
|
54
|
+
createdAt: action.createdAt,
|
|
55
|
+
undoneAt: action.undoneAt,
|
|
56
|
+
undoneByName: userName(store, action.undoneBy),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const RESULT_WORDS = [
|
|
60
|
+
["ok", "OK"],
|
|
61
|
+
["ko", "KO"],
|
|
62
|
+
["blocked", "bloqué"],
|
|
63
|
+
];
|
|
64
|
+
/** Ligne d'une demande dans la file. */
|
|
65
|
+
function requestTitle(store, request) {
|
|
66
|
+
const task = request.contextTaskId === null ? null : store.findTask(request.contextTaskId);
|
|
67
|
+
if (request.kind === "test_review" && task) {
|
|
68
|
+
const key = `${store.getSpace(task.spaceId).keyPrefix}-${task.number}`;
|
|
69
|
+
const results = store.listTestResults(task.id).filter((result) => result.testerId === request.authorId);
|
|
70
|
+
const counts = RESULT_WORDS.map(([value, word]) => [results.filter((result) => result.result === value).length, word]).filter(([n]) => n > 0);
|
|
71
|
+
return counts.length > 0 ? `Review de test ${key} : ${counts.map(([n, word]) => `${n} ${word}`).join(", ")}` : `Review de test ${key}`;
|
|
72
|
+
}
|
|
73
|
+
if (request.kind === "idea_promotion") {
|
|
74
|
+
const idea = request.contextIdeaId === null ? null : store.findIdea(request.contextIdeaId);
|
|
75
|
+
return idea ? `Promouvoir l'idée « ${idea.text.trim().split(/\r?\n/)[0]} »` : request.text;
|
|
76
|
+
}
|
|
77
|
+
return request.text;
|
|
78
|
+
}
|
|
79
|
+
export function requestView(store, userId, request) {
|
|
80
|
+
const author = store.getUser(request.authorId);
|
|
81
|
+
const target = store.getSpace(request.targetSpaceId);
|
|
82
|
+
const task = request.contextTaskId === null ? null : store.findTask(request.contextTaskId);
|
|
83
|
+
const member = store.isMember(request.targetSpaceId, userId);
|
|
84
|
+
const actions = member ? store.listAgentActions(request.id).map((action) => actionView(store, action)) : [];
|
|
85
|
+
const claimer = request.claimedByTokenId === null ? null : store.findAgentToken(request.claimedByTokenId);
|
|
86
|
+
return {
|
|
87
|
+
id: request.id,
|
|
88
|
+
kind: request.kind,
|
|
89
|
+
status: request.status,
|
|
90
|
+
text: request.text,
|
|
91
|
+
title: requestTitle(store, request),
|
|
92
|
+
authorId: author.id,
|
|
93
|
+
authorName: author.displayName ?? author.email,
|
|
94
|
+
authorEmail: author.email,
|
|
95
|
+
targetSpaceId: target.id,
|
|
96
|
+
targetSpaceName: target.name,
|
|
97
|
+
contextTask: task ? { id: task.id, key: `${store.getSpace(task.spaceId).keyPrefix}-${task.number}`, title: task.title } : null,
|
|
98
|
+
reply: request.reply,
|
|
99
|
+
failureReason: request.failureReason,
|
|
100
|
+
createdAt: request.createdAt,
|
|
101
|
+
updatedAt: request.updatedAt,
|
|
102
|
+
claimedAt: request.claimedAt,
|
|
103
|
+
agent: claimer ? agentRef(store, claimer) : null,
|
|
104
|
+
completedAt: request.completedAt,
|
|
105
|
+
cancelledByName: userName(store, request.cancelledBy),
|
|
106
|
+
canEdit: request.status === "pending" && member,
|
|
107
|
+
canRelease: request.status === "processing" && member,
|
|
108
|
+
canUndo: member && (request.status === "done" || request.status === "failed"),
|
|
109
|
+
actions,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// --- Demandes -------------------------------------------------------------------
|
|
113
|
+
/** Demande visible par l'utilisateur : membre de son espace. */
|
|
114
|
+
function requireVisibleRequest(store, userId, requestId) {
|
|
115
|
+
const request = store.findAgentRequest(requestId);
|
|
116
|
+
if (!request || !store.isMember(request.targetSpaceId, userId)) {
|
|
117
|
+
throw new NotFoundError("Demande introuvable, ou vous n'êtes pas membre de l'espace où elle a été écrite.");
|
|
118
|
+
}
|
|
119
|
+
return request;
|
|
120
|
+
}
|
|
121
|
+
const EDIT_ACTIONS = {
|
|
122
|
+
edit: "Impossible de modifier cette demande",
|
|
123
|
+
cancel: "Impossible d'annuler cette demande",
|
|
124
|
+
};
|
|
125
|
+
/** Demande en attente qu'un membre de son espace modifie ou annule. */
|
|
126
|
+
function requireEditableRequest(store, userId, requestId, action) {
|
|
127
|
+
const request = requireVisibleRequest(store, userId, requestId);
|
|
128
|
+
const impossible = EDIT_ACTIONS[action];
|
|
129
|
+
if (request.status !== "pending") {
|
|
130
|
+
const why = request.status === "cancelled" ? "elle est annulée" : "l'agent l'a déjà prise";
|
|
131
|
+
throw new InvariantError(`${impossible} : ${why}.`);
|
|
132
|
+
}
|
|
133
|
+
return request;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Dépose une demande de l'utilisateur, écrite dans l'espace `spaceId`, qui
|
|
137
|
+
* est aussi l'espace où l'agent agira. La tâche de contexte appartient à cet
|
|
138
|
+
* espace ; une promotion d'idée porte une idée de cet espace.
|
|
139
|
+
*/
|
|
140
|
+
export function createRequest(store, userId, spaceId, input) {
|
|
141
|
+
const request = store.mutate(asUser(userId), (tx) => {
|
|
142
|
+
requireMember(tx.store, spaceId, userId);
|
|
143
|
+
const contextTaskId = input.contextTaskId ?? null;
|
|
144
|
+
if (contextTaskId !== null) {
|
|
145
|
+
const { task } = requireTask(tx.store, contextTaskId, userId);
|
|
146
|
+
if (task.spaceId !== spaceId)
|
|
147
|
+
throw new OrganizerError("La tâche de contexte doit appartenir à l'espace où la demande est écrite.");
|
|
148
|
+
}
|
|
149
|
+
const contextIdeaId = input.contextIdeaId ?? null;
|
|
150
|
+
if (input.kind === "idea_promotion") {
|
|
151
|
+
const idea = contextIdeaId === null ? null : tx.store.findIdea(contextIdeaId);
|
|
152
|
+
if (!idea || idea.spaceId !== spaceId)
|
|
153
|
+
throw new OrganizerError("Une promotion d'idée porte une idée de l'espace où elle est demandée.");
|
|
154
|
+
}
|
|
155
|
+
return tx.createAgentRequest({ targetSpaceId: spaceId, kind: input.kind, text: input.text, contextTaskId, contextIdeaId });
|
|
156
|
+
});
|
|
157
|
+
return requestView(store, userId, request);
|
|
158
|
+
}
|
|
159
|
+
export function updateRequest(store, userId, requestId, text) {
|
|
160
|
+
const request = store.mutate(asUser(userId), (tx) => {
|
|
161
|
+
requireEditableRequest(tx.store, userId, requestId, "edit");
|
|
162
|
+
return tx.updateAgentRequestText(requestId, text);
|
|
163
|
+
});
|
|
164
|
+
return requestView(store, userId, request);
|
|
165
|
+
}
|
|
166
|
+
export function cancelRequest(store, userId, requestId) {
|
|
167
|
+
const request = store.mutate(asUser(userId), (tx) => {
|
|
168
|
+
requireEditableRequest(tx.store, userId, requestId, "cancel");
|
|
169
|
+
return tx.cancelAgentRequest(requestId);
|
|
170
|
+
});
|
|
171
|
+
return requestView(store, userId, request);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Libère une demande que l'agent a prise sans la terminer : elle redevient
|
|
175
|
+
* `pending` et un agent pourra la reprendre. Les actions déjà faites restent
|
|
176
|
+
* dans son lot. Réservé aux membres de l'espace cible.
|
|
177
|
+
*/
|
|
178
|
+
export function releaseRequest(store, userId, requestId) {
|
|
179
|
+
const request = store.mutate(asUser(userId), (tx) => {
|
|
180
|
+
const request = requireVisibleRequest(tx.store, userId, requestId);
|
|
181
|
+
if (request.status !== "processing") {
|
|
182
|
+
throw new InvariantError("Seule une demande en cours de traitement se libère : celle-ci n'a pas été prise par l'agent, ou elle est terminée.");
|
|
183
|
+
}
|
|
184
|
+
return tx.releaseAgentRequest(requestId);
|
|
185
|
+
});
|
|
186
|
+
return requestView(store, userId, request);
|
|
187
|
+
}
|
|
188
|
+
export function getRequest(store, userId, requestId) {
|
|
189
|
+
return requestView(store, userId, requireVisibleRequest(store, userId, requestId));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Demandes d'un espace, avec le nombre de demandes en attente et les jetons
|
|
193
|
+
* actifs qui couvrent l'espace, chacun avec son dernier usage et la présence
|
|
194
|
+
* de son veilleur de notifications.
|
|
195
|
+
*/
|
|
196
|
+
export function listRequests(store, userId, spaceId, now = new Date()) {
|
|
197
|
+
requireMember(store, spaceId, userId);
|
|
198
|
+
const requests = store.listAgentRequestsOfSpace(spaceId, REQUESTS_PAGE_SIZE).map((request) => requestView(store, userId, request));
|
|
199
|
+
const agents = store
|
|
200
|
+
.listAgentTokensOfSpace(spaceId)
|
|
201
|
+
.filter((token) => tokenState(store, token, now) === "active")
|
|
202
|
+
.map((token) => ({ ...agentRef(store, token), lastUsedAt: token.lastUsedAt, listening: store.watchers.status(token.id).armed }));
|
|
203
|
+
return { requests, pendingCount: store.countPendingRequestsOfSpace(spaceId), agents };
|
|
204
|
+
}
|
|
205
|
+
// --- Annulation -----------------------------------------------------------------
|
|
206
|
+
/**
|
|
207
|
+
* Demande visible dont l'utilisateur peut annuler les actions : l'agent l'a
|
|
208
|
+
* terminée. Les membres de son espace (le propriétaire seul pour un espace
|
|
209
|
+
* personnel) annulent ses actions.
|
|
210
|
+
*/
|
|
211
|
+
function requireUndoableRequest(request) {
|
|
212
|
+
if (request.status === "processing")
|
|
213
|
+
throw new InvariantError("L'agent traite encore cette demande : attendez qu'il ait terminé pour annuler ses actions.");
|
|
214
|
+
}
|
|
215
|
+
/** Annule une action de l'agent. */
|
|
216
|
+
export function undoAction(store, userId, actionId) {
|
|
217
|
+
const requestId = store.mutate(asUser(userId), (tx) => {
|
|
218
|
+
const action = tx.store.findAgentAction(actionId);
|
|
219
|
+
if (!action)
|
|
220
|
+
throw new NotFoundError("Action introuvable.");
|
|
221
|
+
const request = requireVisibleRequest(tx.store, userId, action.requestId);
|
|
222
|
+
requireUndoableRequest(request);
|
|
223
|
+
undoAgentAction(tx, action);
|
|
224
|
+
return request.id;
|
|
225
|
+
});
|
|
226
|
+
return requestView(store, userId, store.getAgentRequest(requestId));
|
|
227
|
+
}
|
|
228
|
+
/** Annule toutes les actions non annulées d'un lot, de la plus récente à la plus ancienne : tout le lot ou rien. */
|
|
229
|
+
export function undoRequest(store, userId, requestId) {
|
|
230
|
+
store.mutate(asUser(userId), (tx) => {
|
|
231
|
+
const request = requireVisibleRequest(tx.store, userId, requestId);
|
|
232
|
+
requireUndoableRequest(request);
|
|
233
|
+
const remaining = tx.store.listAgentActions(requestId).filter((action) => action.undoneAt === null);
|
|
234
|
+
if (remaining.length === 0)
|
|
235
|
+
throw new InvariantError("Toutes les actions de ce lot sont déjà annulées.");
|
|
236
|
+
for (const action of remaining.reverse())
|
|
237
|
+
undoAgentAction(tx, tx.store.getAgentAction(action.id));
|
|
238
|
+
});
|
|
239
|
+
return requestView(store, userId, store.getAgentRequest(requestId));
|
|
240
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Store } from "./store.js";
|
|
3
|
+
import type { EntityType } from "../../shared/schemas/enums.schema.js";
|
|
4
|
+
import { type AgentAction, type ChecklistItem, type Comment, type Idea, type TaskSnapshot } from "../../shared/schemas/entities.schema.js";
|
|
5
|
+
/** Instantanés des entités que l'agent modifie : l'état avant et après chaque action de son lot. */
|
|
6
|
+
/** Point de vérification avec les résultats saisis dessus. */
|
|
7
|
+
export declare const CheckpointSnapshotSchema: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
taskId: z.ZodString;
|
|
10
|
+
text: z.ZodString;
|
|
11
|
+
position: z.ZodNumber;
|
|
12
|
+
results: z.ZodArray<z.ZodObject<{
|
|
13
|
+
checkpointId: z.ZodString;
|
|
14
|
+
testerId: z.ZodString;
|
|
15
|
+
result: z.ZodEnum<{
|
|
16
|
+
blocked: "blocked";
|
|
17
|
+
ko: "ko";
|
|
18
|
+
ok: "ok";
|
|
19
|
+
untested: "untested";
|
|
20
|
+
}>;
|
|
21
|
+
comment: z.ZodString;
|
|
22
|
+
updatedAt: z.ZodISODateTime;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type CheckpointSnapshot = z.infer<typeof CheckpointSnapshotSchema>;
|
|
26
|
+
/** Entités dont l'agent enregistre l'état avant et après chaque action. */
|
|
27
|
+
export type TrackedEntityType = "task" | "checklist_item" | "test_checkpoint" | "comment" | "idea";
|
|
28
|
+
export declare function isTracked(entityType: EntityType): entityType is TrackedEntityType;
|
|
29
|
+
/** Instantané courant d'une entité, ou null si elle n'existe pas. */
|
|
30
|
+
export declare function snapshotOf(store: Store, entityType: TrackedEntityType, entityId: string): TaskSnapshot | ChecklistItem | CheckpointSnapshot | Comment | Idea | null;
|
|
31
|
+
/** Tâche concernée par une action : la tâche elle-même, ou celle qui porte l'élément ; null pour une idée. */
|
|
32
|
+
export declare function taskIdOfAction(action: Pick<AgentAction, "entityType" | "entityId" | "before" | "after">): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TestCheckpointSchema, TestResultSchema } from "../../shared/schemas/entities.schema.js";
|
|
3
|
+
/** Instantanés des entités que l'agent modifie : l'état avant et après chaque action de son lot. */
|
|
4
|
+
/** Point de vérification avec les résultats saisis dessus. */
|
|
5
|
+
export const CheckpointSnapshotSchema = TestCheckpointSchema.extend({ results: z.array(TestResultSchema) });
|
|
6
|
+
const TRACKED = new Set(["task", "checklist_item", "test_checkpoint", "comment", "idea"]);
|
|
7
|
+
export function isTracked(entityType) {
|
|
8
|
+
return TRACKED.has(entityType);
|
|
9
|
+
}
|
|
10
|
+
/** Instantané courant d'une entité, ou null si elle n'existe pas. */
|
|
11
|
+
export function snapshotOf(store, entityType, entityId) {
|
|
12
|
+
switch (entityType) {
|
|
13
|
+
case "task":
|
|
14
|
+
return store.findTask(entityId) ? store.taskSnapshot(entityId) : null;
|
|
15
|
+
case "checklist_item":
|
|
16
|
+
return store.findChecklistItem(entityId);
|
|
17
|
+
case "test_checkpoint": {
|
|
18
|
+
const checkpoint = store.findCheckpoint(entityId);
|
|
19
|
+
if (!checkpoint)
|
|
20
|
+
return null;
|
|
21
|
+
return { ...checkpoint, results: store.listTestResults(checkpoint.taskId).filter((result) => result.checkpointId === entityId) };
|
|
22
|
+
}
|
|
23
|
+
case "comment":
|
|
24
|
+
return store.findComment(entityId);
|
|
25
|
+
case "idea":
|
|
26
|
+
return store.findIdea(entityId);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Tâche concernée par une action : la tâche elle-même, ou celle qui porte l'élément ; null pour une idée. */
|
|
30
|
+
export function taskIdOfAction(action) {
|
|
31
|
+
if (action.entityType === "task")
|
|
32
|
+
return action.entityId;
|
|
33
|
+
const snapshot = (action.after ?? action.before);
|
|
34
|
+
return typeof snapshot?.taskId === "string" ? snapshot.taskId : null;
|
|
35
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AttachmentStorage } from "./attachments.js";
|
|
2
|
+
import type { Mailer } from "./mail.js";
|
|
3
|
+
import type { Store } from "./store.js";
|
|
4
|
+
import type { InviteResponse, MembersResponse, ReceivedInvitationsResponse, ResendInvitationResponse, SpaceSummary } from "../../shared/schemas/api.schema.js";
|
|
5
|
+
import type { Membership, Space } from "../../shared/schemas/entities.schema.js";
|
|
6
|
+
/**
|
|
7
|
+
* Espaces, membres et invitations, avec les droits de la GUI : un membre lit
|
|
8
|
+
* l'espace ; seul le propriétaire invite, révoque, retire un membre, renomme
|
|
9
|
+
* ou supprime l'espace ; un membre peut quitter un espace commun ; le
|
|
10
|
+
* destinataire d'une invitation l'accepte ou la refuse. Les droits sont
|
|
11
|
+
* vérifiés dans la transaction de l'écriture.
|
|
12
|
+
*/
|
|
13
|
+
/** Dépendances de l'envoi des invitations. */
|
|
14
|
+
export interface InvitationDeps {
|
|
15
|
+
mailer: Mailer;
|
|
16
|
+
/** URL publique de la GUI (`ORGANIZER_PUBLIC_URL`), base des liens envoyés par e-mail. */
|
|
17
|
+
publicUrl: string | undefined;
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
}
|
|
20
|
+
/** Espace dont `userId` est membre ; un non-membre reçoit la même réponse qu'un espace inexistant. */
|
|
21
|
+
export declare function requireMember(store: Store, spaceId: string, userId: string): {
|
|
22
|
+
space: Space;
|
|
23
|
+
membership: Membership;
|
|
24
|
+
};
|
|
25
|
+
/** Espace commun dont `userId` est le propriétaire. */
|
|
26
|
+
export declare function requireOwner(store: Store, spaceId: string, userId: string, action: string): Space;
|
|
27
|
+
/** Espaces de l'utilisateur : son espace personnel d'abord, puis les espaces communs par nom. */
|
|
28
|
+
export declare function listSpaces(store: Store, userId: string): SpaceSummary[];
|
|
29
|
+
/** Crée un espace commun dont l'utilisateur est propriétaire ; le préfixe est dérivé du nom s'il est omis. */
|
|
30
|
+
export declare function createSharedSpace(store: Store, userId: string, input: {
|
|
31
|
+
name: string;
|
|
32
|
+
keyPrefix?: string;
|
|
33
|
+
}): SpaceSummary;
|
|
34
|
+
export declare function renameSpace(store: Store, userId: string, spaceId: string, name: string): SpaceSummary;
|
|
35
|
+
/** Supprime un espace commun, son contenu et les fichiers de ses pièces jointes. */
|
|
36
|
+
export declare function deleteSpace(store: Store, storage: AttachmentStorage, userId: string, spaceId: string): void;
|
|
37
|
+
/** Membres (propriétaire d'abord) et invitations en attente d'un espace, pour un de ses membres. */
|
|
38
|
+
export declare function listMembers(store: Store, userId: string, spaceId: string, now?: Date): MembersResponse;
|
|
39
|
+
/** Le propriétaire retire un membre d'un espace commun. */
|
|
40
|
+
export declare function removeMember(store: Store, userId: string, spaceId: string, memberId: string): void;
|
|
41
|
+
/** Un membre quitte un espace commun ; le propriétaire ne le peut pas. */
|
|
42
|
+
export declare function leaveSpace(store: Store, userId: string, spaceId: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Le propriétaire invite une adresse. Un compte actif devient membre
|
|
45
|
+
* directement ; une adresse inconnue reçoit un compte `invited` ; un compte
|
|
46
|
+
* `invited` reçoit l'invitation en plus des siennes. Dans ces deux derniers
|
|
47
|
+
* cas, un lien d'activation propre à cette invitation est émis, renvoyé à
|
|
48
|
+
* l'invitant et envoyé par e-mail ; les liens des autres invitations du compte
|
|
49
|
+
* restent valables.
|
|
50
|
+
*/
|
|
51
|
+
export declare function inviteToSpace(store: Store, deps: InvitationDeps, userId: string, spaceId: string, rawEmail: string): Promise<InviteResponse>;
|
|
52
|
+
/**
|
|
53
|
+
* Renvoie une invitation avec une nouvelle échéance. Un compte encore à
|
|
54
|
+
* activer reçoit un nouveau lien, qui remplace le lien précédent de cette
|
|
55
|
+
* invitation seulement ; un compte déjà actif retrouve l'invitation dans ses
|
|
56
|
+
* « Invitations reçues », sans lien.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resendInvitation(store: Store, deps: InvitationDeps, userId: string, spaceId: string, invitationId: string): Promise<ResendInvitationResponse>;
|
|
59
|
+
/** Révoque une invitation ; son lien d'activation ne fonctionne plus. */
|
|
60
|
+
export declare function revokeInvitation(store: Store, userId: string, spaceId: string, invitationId: string): void;
|
|
61
|
+
/** Invitations en attente et non expirées adressées à l'utilisateur, par date. */
|
|
62
|
+
export declare function listReceivedInvitations(store: Store, userId: string, now?: Date): ReceivedInvitationsResponse;
|
|
63
|
+
/** L'utilisateur accepte une invitation qui lui est adressée et rejoint l'espace. */
|
|
64
|
+
export declare function acceptReceivedInvitation(store: Store, userId: string, invitationId: string, now?: Date): SpaceSummary;
|
|
65
|
+
/** L'utilisateur refuse une invitation qui lui est adressée. */
|
|
66
|
+
export declare function declineReceivedInvitation(store: Store, userId: string, invitationId: string): void;
|