@imenam/mcp-messenger 1.0.35 → 1.0.36
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 +8 -2
- package/dist/src/core/access-store.d.ts +5 -0
- package/dist/src/core/access-store.js +17 -0
- package/dist/src/core/store.js +223 -223
- package/dist/src/index.js +21 -21
- package/dist/src/server/app.js +6 -3
- package/dist/src/server/ui/app.js +62 -1
- package/dist/src/server/ui/markdown.js +340 -340
- package/dist/src/server/ui/styles.css +3826 -3820
- package/package.json +64 -61
- package/dist/src/cli/generate-token.d.ts +0 -7
- package/dist/src/cli/generate-token.js +0 -12
package/dist/src/core/store.js
CHANGED
|
@@ -22,9 +22,9 @@ const LINK_NOTE_MAX = 500;
|
|
|
22
22
|
* touchée. Une boîte qui ne fait que recevoir reste vivante, alors que `last_seen_at`
|
|
23
23
|
* seul la ferait paraître abandonnée.
|
|
24
24
|
*/
|
|
25
|
-
const LAST_ACTIVITY = `MAX(
|
|
26
|
-
COALESCE(a.last_seen_at, ''),
|
|
27
|
-
COALESCE((SELECT MAX(d.created_at) FROM deliveries d WHERE d.agent_id = a.id), '')
|
|
25
|
+
const LAST_ACTIVITY = `MAX(
|
|
26
|
+
COALESCE(a.last_seen_at, ''),
|
|
27
|
+
COALESCE((SELECT MAX(d.created_at) FROM deliveries d WHERE d.agent_id = a.id), '')
|
|
28
28
|
)`;
|
|
29
29
|
/** Colonnes du fil jointes à tout message : le titre voyage avec chaque livraison. */
|
|
30
30
|
const THREAD_JOIN = "LEFT JOIN threads t ON t.id = m.thread_id";
|
|
@@ -37,10 +37,10 @@ const THREAD_JOIN = "LEFT JOIN threads t ON t.id = m.thread_id";
|
|
|
37
37
|
* pose. Un message n'ayant qu'une copie d'expéditeur (destinataire jamais enregistré) n'a
|
|
38
38
|
* pas de ligne ici : `read_at` reste alors nul, donc non lu.
|
|
39
39
|
*/
|
|
40
|
-
const ADMIN_DELIVERY = `'to' AS role,
|
|
41
|
-
(SELECT dr.read_at FROM deliveries dr WHERE dr.message_id = m.id AND dr.role != 'sender'
|
|
42
|
-
ORDER BY dr.agent_id LIMIT 1) AS read_at,
|
|
43
|
-
COALESCE((SELECT dr.folder FROM deliveries dr WHERE dr.message_id = m.id AND dr.role != 'sender'
|
|
40
|
+
const ADMIN_DELIVERY = `'to' AS role,
|
|
41
|
+
(SELECT dr.read_at FROM deliveries dr WHERE dr.message_id = m.id AND dr.role != 'sender'
|
|
42
|
+
ORDER BY dr.agent_id LIMIT 1) AS read_at,
|
|
43
|
+
COALESCE((SELECT dr.folder FROM deliveries dr WHERE dr.message_id = m.id AND dr.role != 'sender'
|
|
44
44
|
ORDER BY dr.agent_id LIMIT 1), 'inbox') AS folder`;
|
|
45
45
|
/**
|
|
46
46
|
* Boîte aux lettres persistée en SQLite.
|
|
@@ -66,108 +66,108 @@ export class MessageStore extends EventEmitter {
|
|
|
66
66
|
this.access = new AccessStore(this.db);
|
|
67
67
|
}
|
|
68
68
|
migrate() {
|
|
69
|
-
this.db.exec(`
|
|
70
|
-
CREATE TABLE IF NOT EXISTS agents (
|
|
71
|
-
id TEXT PRIMARY KEY,
|
|
72
|
-
agent_name TEXT,
|
|
73
|
-
agent_description TEXT,
|
|
74
|
-
display_name TEXT,
|
|
75
|
-
description TEXT,
|
|
76
|
-
created_at TEXT NOT NULL,
|
|
77
|
-
last_seen_at TEXT,
|
|
78
|
-
registered INTEGER NOT NULL DEFAULT 0
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
82
|
-
id TEXT PRIMARY KEY,
|
|
83
|
-
thread_id TEXT NOT NULL,
|
|
84
|
-
sender_id TEXT NOT NULL,
|
|
85
|
-
subject TEXT NOT NULL,
|
|
86
|
-
body TEXT NOT NULL,
|
|
87
|
-
priority TEXT NOT NULL DEFAULT 'normal',
|
|
88
|
-
in_reply_to TEXT,
|
|
89
|
-
created_at TEXT NOT NULL
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
CREATE TABLE IF NOT EXISTS deliveries (
|
|
93
|
-
id TEXT PRIMARY KEY,
|
|
94
|
-
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
95
|
-
agent_id TEXT NOT NULL,
|
|
96
|
-
role TEXT NOT NULL,
|
|
97
|
-
folder TEXT NOT NULL,
|
|
98
|
-
read_at TEXT,
|
|
99
|
-
starred INTEGER NOT NULL DEFAULT 0,
|
|
100
|
-
created_at TEXT NOT NULL,
|
|
101
|
-
UNIQUE (message_id, agent_id, role)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
CREATE TABLE IF NOT EXISTS threads (
|
|
105
|
-
id TEXT PRIMARY KEY,
|
|
106
|
-
title TEXT,
|
|
107
|
-
description TEXT,
|
|
108
|
-
named_by TEXT,
|
|
109
|
-
created_at TEXT NOT NULL,
|
|
110
|
-
updated_at TEXT
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
CREATE TABLE IF NOT EXISTS folders (
|
|
114
|
-
agent_id TEXT NOT NULL,
|
|
115
|
-
name TEXT NOT NULL,
|
|
116
|
-
created_at TEXT NOT NULL,
|
|
117
|
-
PRIMARY KEY (agent_id, name)
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
CREATE TABLE IF NOT EXISTS attachments (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
123
|
-
position INTEGER NOT NULL,
|
|
124
|
-
filename TEXT NOT NULL,
|
|
125
|
-
mime_type TEXT NOT NULL,
|
|
126
|
-
size INTEGER NOT NULL,
|
|
127
|
-
data BLOB NOT NULL,
|
|
128
|
-
created_at TEXT NOT NULL
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
CREATE TABLE IF NOT EXISTS links (
|
|
132
|
-
id TEXT PRIMARY KEY,
|
|
133
|
-
mailbox_a TEXT NOT NULL,
|
|
134
|
-
mailbox_b TEXT NOT NULL,
|
|
135
|
-
note TEXT,
|
|
136
|
-
created_at TEXT NOT NULL,
|
|
137
|
-
seen_by_a_at TEXT,
|
|
138
|
-
seen_by_b_at TEXT,
|
|
139
|
-
UNIQUE (mailbox_a, mailbox_b)
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
CREATE INDEX IF NOT EXISTS idx_links_b ON links (mailbox_b);
|
|
143
|
-
CREATE INDEX IF NOT EXISTS idx_attachments_message ON attachments (message_id, position);
|
|
144
|
-
CREATE INDEX IF NOT EXISTS idx_deliveries_mailbox ON deliveries (agent_id, folder, created_at DESC);
|
|
145
|
-
CREATE INDEX IF NOT EXISTS idx_deliveries_message ON deliveries (message_id);
|
|
146
|
-
CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages (thread_id, created_at);
|
|
69
|
+
this.db.exec(`
|
|
70
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
agent_name TEXT,
|
|
73
|
+
agent_description TEXT,
|
|
74
|
+
display_name TEXT,
|
|
75
|
+
description TEXT,
|
|
76
|
+
created_at TEXT NOT NULL,
|
|
77
|
+
last_seen_at TEXT,
|
|
78
|
+
registered INTEGER NOT NULL DEFAULT 0
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
thread_id TEXT NOT NULL,
|
|
84
|
+
sender_id TEXT NOT NULL,
|
|
85
|
+
subject TEXT NOT NULL,
|
|
86
|
+
body TEXT NOT NULL,
|
|
87
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
88
|
+
in_reply_to TEXT,
|
|
89
|
+
created_at TEXT NOT NULL
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS deliveries (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
95
|
+
agent_id TEXT NOT NULL,
|
|
96
|
+
role TEXT NOT NULL,
|
|
97
|
+
folder TEXT NOT NULL,
|
|
98
|
+
read_at TEXT,
|
|
99
|
+
starred INTEGER NOT NULL DEFAULT 0,
|
|
100
|
+
created_at TEXT NOT NULL,
|
|
101
|
+
UNIQUE (message_id, agent_id, role)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
title TEXT,
|
|
107
|
+
description TEXT,
|
|
108
|
+
named_by TEXT,
|
|
109
|
+
created_at TEXT NOT NULL,
|
|
110
|
+
updated_at TEXT
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS folders (
|
|
114
|
+
agent_id TEXT NOT NULL,
|
|
115
|
+
name TEXT NOT NULL,
|
|
116
|
+
created_at TEXT NOT NULL,
|
|
117
|
+
PRIMARY KEY (agent_id, name)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS attachments (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
123
|
+
position INTEGER NOT NULL,
|
|
124
|
+
filename TEXT NOT NULL,
|
|
125
|
+
mime_type TEXT NOT NULL,
|
|
126
|
+
size INTEGER NOT NULL,
|
|
127
|
+
data BLOB NOT NULL,
|
|
128
|
+
created_at TEXT NOT NULL
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
CREATE TABLE IF NOT EXISTS links (
|
|
132
|
+
id TEXT PRIMARY KEY,
|
|
133
|
+
mailbox_a TEXT NOT NULL,
|
|
134
|
+
mailbox_b TEXT NOT NULL,
|
|
135
|
+
note TEXT,
|
|
136
|
+
created_at TEXT NOT NULL,
|
|
137
|
+
seen_by_a_at TEXT,
|
|
138
|
+
seen_by_b_at TEXT,
|
|
139
|
+
UNIQUE (mailbox_a, mailbox_b)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_links_b ON links (mailbox_b);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_attachments_message ON attachments (message_id, position);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_mailbox ON deliveries (agent_id, folder, created_at DESC);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_message ON deliveries (message_id);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages (thread_id, created_at);
|
|
147
147
|
`);
|
|
148
148
|
// Bases antérieures à la séparation agent / boîte : `display_name` y tenait les deux
|
|
149
149
|
// rôles, et venait presque toujours de la configuration (le nom du projet). On le
|
|
150
150
|
// déplace vers l'identité de l'agent ; la boîte repart sans nom, à poser par l'agent.
|
|
151
151
|
const agentColumns = this.db.prepare("PRAGMA table_info(agents)").all().map((c) => c.name);
|
|
152
152
|
if (!agentColumns.includes("agent_name")) {
|
|
153
|
-
this.db.exec(`
|
|
154
|
-
ALTER TABLE agents ADD COLUMN agent_name TEXT;
|
|
155
|
-
ALTER TABLE agents ADD COLUMN agent_description TEXT;
|
|
156
|
-
UPDATE agents
|
|
157
|
-
SET agent_name = display_name,
|
|
158
|
-
agent_description = description,
|
|
159
|
-
display_name = NULL,
|
|
160
|
-
description = NULL;
|
|
153
|
+
this.db.exec(`
|
|
154
|
+
ALTER TABLE agents ADD COLUMN agent_name TEXT;
|
|
155
|
+
ALTER TABLE agents ADD COLUMN agent_description TEXT;
|
|
156
|
+
UPDATE agents
|
|
157
|
+
SET agent_name = display_name,
|
|
158
|
+
agent_description = description,
|
|
159
|
+
display_name = NULL,
|
|
160
|
+
description = NULL;
|
|
161
161
|
`);
|
|
162
162
|
}
|
|
163
163
|
// Bases antérieures aux fils nommés : chaque fil déjà présent reçoit sa ligne, sans
|
|
164
164
|
// titre. L'affichage retombe alors sur le sujet d'ouverture, comme avant.
|
|
165
|
-
this.db.exec(`
|
|
166
|
-
INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
167
|
-
SELECT m.thread_id, NULL, NULL, NULL, MIN(m.created_at), NULL
|
|
168
|
-
FROM messages m
|
|
169
|
-
WHERE NOT EXISTS (SELECT 1 FROM threads t WHERE t.id = m.thread_id)
|
|
170
|
-
GROUP BY m.thread_id;
|
|
165
|
+
this.db.exec(`
|
|
166
|
+
INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
167
|
+
SELECT m.thread_id, NULL, NULL, NULL, MIN(m.created_at), NULL
|
|
168
|
+
FROM messages m
|
|
169
|
+
WHERE NOT EXISTS (SELECT 1 FROM threads t WHERE t.id = m.thread_id)
|
|
170
|
+
GROUP BY m.thread_id;
|
|
171
171
|
`);
|
|
172
172
|
}
|
|
173
173
|
close() {
|
|
@@ -184,19 +184,19 @@ export class MessageStore extends EventEmitter {
|
|
|
184
184
|
const existing = this.db.prepare("SELECT id FROM agents WHERE id = ?").get(id);
|
|
185
185
|
if (!existing) {
|
|
186
186
|
this.db
|
|
187
|
-
.prepare(`INSERT INTO agents (id, agent_name, agent_description, display_name, description, created_at, last_seen_at, registered)
|
|
187
|
+
.prepare(`INSERT INTO agents (id, agent_name, agent_description, display_name, description, created_at, last_seen_at, registered)
|
|
188
188
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
189
189
|
.run(id, opts.agent_name ?? null, opts.agent_description ?? null, opts.display_name ?? null, opts.description ?? null, now, opts.registered ? now : null, opts.registered ? 1 : 0);
|
|
190
190
|
}
|
|
191
191
|
else {
|
|
192
192
|
this.db
|
|
193
|
-
.prepare(`UPDATE agents
|
|
194
|
-
SET agent_name = COALESCE(?, agent_name),
|
|
195
|
-
agent_description = COALESCE(?, agent_description),
|
|
196
|
-
display_name = COALESCE(?, display_name),
|
|
197
|
-
description = COALESCE(?, description),
|
|
198
|
-
registered = MAX(registered, ?),
|
|
199
|
-
last_seen_at = CASE WHEN ? = 1 THEN ? ELSE last_seen_at END
|
|
193
|
+
.prepare(`UPDATE agents
|
|
194
|
+
SET agent_name = COALESCE(?, agent_name),
|
|
195
|
+
agent_description = COALESCE(?, agent_description),
|
|
196
|
+
display_name = COALESCE(?, display_name),
|
|
197
|
+
description = COALESCE(?, description),
|
|
198
|
+
registered = MAX(registered, ?),
|
|
199
|
+
last_seen_at = CASE WHEN ? = 1 THEN ? ELSE last_seen_at END
|
|
200
200
|
WHERE id = ?`)
|
|
201
201
|
.run(opts.agent_name ?? null, opts.agent_description ?? null, opts.display_name ?? null, opts.description ?? null, opts.registered ? 1 : 0, opts.registered ? 1 : 0, now, id);
|
|
202
202
|
}
|
|
@@ -228,9 +228,9 @@ export class MessageStore extends EventEmitter {
|
|
|
228
228
|
listAgents(exclude) {
|
|
229
229
|
const excluded = exclude ? normalizeAgentId(exclude) : "";
|
|
230
230
|
const rows = this.db
|
|
231
|
-
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at
|
|
232
|
-
FROM agents a
|
|
233
|
-
WHERE a.id != ?
|
|
231
|
+
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at
|
|
232
|
+
FROM agents a
|
|
233
|
+
WHERE a.id != ?
|
|
234
234
|
ORDER BY last_activity_at DESC, a.id`)
|
|
235
235
|
.all(excluded);
|
|
236
236
|
return rows.map((r) => ({
|
|
@@ -288,23 +288,23 @@ export class MessageStore extends EventEmitter {
|
|
|
288
288
|
// Le fil naît avec son premier message ; sur une réponse, la ligne existe déjà et
|
|
289
289
|
// n'est touchée que si l'expéditeur fournit un nouveau titre.
|
|
290
290
|
this.db
|
|
291
|
-
.prepare(`INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
292
|
-
VALUES (?, NULL, NULL, NULL, ?, NULL)
|
|
291
|
+
.prepare(`INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
292
|
+
VALUES (?, NULL, NULL, NULL, ?, NULL)
|
|
293
293
|
ON CONFLICT (id) DO NOTHING`)
|
|
294
294
|
.run(threadId, now);
|
|
295
295
|
if (title !== null || threadDescription !== null) {
|
|
296
296
|
this.renameThreadRow(threadId, from, title, threadDescription, now);
|
|
297
297
|
}
|
|
298
298
|
this.db
|
|
299
|
-
.prepare(`INSERT INTO messages (id, thread_id, sender_id, subject, body, priority, in_reply_to, created_at)
|
|
299
|
+
.prepare(`INSERT INTO messages (id, thread_id, sender_id, subject, body, priority, in_reply_to, created_at)
|
|
300
300
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
301
301
|
.run(messageId, threadId, from, subject, input.body, priority, inReplyTo, now);
|
|
302
|
-
const insertAttachment = this.db.prepare(`INSERT INTO attachments (id, message_id, position, filename, mime_type, size, data, created_at)
|
|
302
|
+
const insertAttachment = this.db.prepare(`INSERT INTO attachments (id, message_id, position, filename, mime_type, size, data, created_at)
|
|
303
303
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
304
304
|
attachments.forEach((a, position) => {
|
|
305
305
|
insertAttachment.run(newId("att"), messageId, position, a.filename, a.mime_type, a.data.length, a.data, now);
|
|
306
306
|
});
|
|
307
|
-
const insert = this.db.prepare(`INSERT INTO deliveries (id, message_id, agent_id, role, folder, read_at, starred, created_at)
|
|
307
|
+
const insert = this.db.prepare(`INSERT INTO deliveries (id, message_id, agent_id, role, folder, read_at, starred, created_at)
|
|
308
308
|
VALUES (?, ?, ?, ?, ?, ?, 0, ?)`);
|
|
309
309
|
for (const d of deliveries) {
|
|
310
310
|
insert.run(newId("dlv"), messageId, d.agent_id, d.role, d.folder, d.read_at, now);
|
|
@@ -349,13 +349,13 @@ export class MessageStore extends EventEmitter {
|
|
|
349
349
|
params.push(like, like);
|
|
350
350
|
}
|
|
351
351
|
const rows = this.db
|
|
352
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
353
|
-
m.priority, m.in_reply_to, m.created_at, d.folder, d.role, d.read_at, d.starred
|
|
354
|
-
FROM deliveries d
|
|
355
|
-
JOIN messages m ON m.id = d.message_id
|
|
356
|
-
${THREAD_JOIN}
|
|
357
|
-
WHERE ${where.join(" AND ")}
|
|
358
|
-
ORDER BY m.created_at DESC, m.id DESC
|
|
352
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
353
|
+
m.priority, m.in_reply_to, m.created_at, d.folder, d.role, d.read_at, d.starred
|
|
354
|
+
FROM deliveries d
|
|
355
|
+
JOIN messages m ON m.id = d.message_id
|
|
356
|
+
${THREAD_JOIN}
|
|
357
|
+
WHERE ${where.join(" AND ")}
|
|
358
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
359
359
|
LIMIT ? OFFSET ?`)
|
|
360
360
|
.all(...params, limit, offset);
|
|
361
361
|
const related = this.relatedFor(rows.map((r) => r.message_id));
|
|
@@ -363,13 +363,13 @@ export class MessageStore extends EventEmitter {
|
|
|
363
363
|
}
|
|
364
364
|
getMessage(agentId, messageId, markRead = false) {
|
|
365
365
|
const row = this.db
|
|
366
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
367
|
-
m.priority, m.in_reply_to, m.created_at, d.folder, d.role, d.read_at, d.starred
|
|
368
|
-
FROM deliveries d
|
|
369
|
-
JOIN messages m ON m.id = d.message_id
|
|
370
|
-
${THREAD_JOIN}
|
|
371
|
-
WHERE d.agent_id = ? AND d.message_id = ?
|
|
372
|
-
ORDER BY CASE d.role WHEN 'to' THEN 0 ELSE 1 END
|
|
366
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
367
|
+
m.priority, m.in_reply_to, m.created_at, d.folder, d.role, d.read_at, d.starred
|
|
368
|
+
FROM deliveries d
|
|
369
|
+
JOIN messages m ON m.id = d.message_id
|
|
370
|
+
${THREAD_JOIN}
|
|
371
|
+
WHERE d.agent_id = ? AND d.message_id = ?
|
|
372
|
+
ORDER BY CASE d.role WHEN 'to' THEN 0 ELSE 1 END
|
|
373
373
|
LIMIT 1`)
|
|
374
374
|
.get(agentId, messageId);
|
|
375
375
|
if (!row)
|
|
@@ -387,14 +387,14 @@ export class MessageStore extends EventEmitter {
|
|
|
387
387
|
/** Tous les messages d'un fil visibles par l'agent, du plus ancien au plus récent. */
|
|
388
388
|
getThread(agentId, threadId) {
|
|
389
389
|
const rows = this.db
|
|
390
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
391
|
-
m.priority, m.in_reply_to, m.created_at,
|
|
392
|
-
MIN(d.folder) AS folder, MIN(d.role) AS role, MIN(d.read_at) AS read_at, MAX(d.starred) AS starred
|
|
393
|
-
FROM deliveries d
|
|
394
|
-
JOIN messages m ON m.id = d.message_id
|
|
395
|
-
${THREAD_JOIN}
|
|
396
|
-
WHERE d.agent_id = ? AND m.thread_id = ?
|
|
397
|
-
GROUP BY m.id
|
|
390
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
391
|
+
m.priority, m.in_reply_to, m.created_at,
|
|
392
|
+
MIN(d.folder) AS folder, MIN(d.role) AS role, MIN(d.read_at) AS read_at, MAX(d.starred) AS starred
|
|
393
|
+
FROM deliveries d
|
|
394
|
+
JOIN messages m ON m.id = d.message_id
|
|
395
|
+
${THREAD_JOIN}
|
|
396
|
+
WHERE d.agent_id = ? AND m.thread_id = ?
|
|
397
|
+
GROUP BY m.id
|
|
398
398
|
ORDER BY m.created_at ASC`)
|
|
399
399
|
.all(agentId, threadId);
|
|
400
400
|
if (rows.length === 0)
|
|
@@ -405,9 +405,9 @@ export class MessageStore extends EventEmitter {
|
|
|
405
405
|
/** Pièce jointe d'un message livré à cet agent, contenu compris. */
|
|
406
406
|
getAttachment(agentId, attachmentId) {
|
|
407
407
|
const row = this.db
|
|
408
|
-
.prepare(`SELECT a.id AS attachment_id, a.message_id, a.filename, a.mime_type, a.size, a.data
|
|
409
|
-
FROM attachments a
|
|
410
|
-
WHERE a.id = ?
|
|
408
|
+
.prepare(`SELECT a.id AS attachment_id, a.message_id, a.filename, a.mime_type, a.size, a.data
|
|
409
|
+
FROM attachments a
|
|
410
|
+
WHERE a.id = ?
|
|
411
411
|
AND EXISTS (SELECT 1 FROM deliveries d WHERE d.message_id = a.message_id AND d.agent_id = ?)`)
|
|
412
412
|
.get(attachmentId, agentId);
|
|
413
413
|
if (!row)
|
|
@@ -452,7 +452,7 @@ export class MessageStore extends EventEmitter {
|
|
|
452
452
|
// qu'elle ait écrit ou seulement reçu. Les paramètres du WHERE se lient avant ceux du
|
|
453
453
|
// HAVING : cette condition doit donc être poussée avant celles de la recherche.
|
|
454
454
|
if (opts.with_agent) {
|
|
455
|
-
where.push(`EXISTS (SELECT 1 FROM deliveries d2 JOIN messages m2 ON m2.id = d2.message_id
|
|
455
|
+
where.push(`EXISTS (SELECT 1 FROM deliveries d2 JOIN messages m2 ON m2.id = d2.message_id
|
|
456
456
|
WHERE m2.thread_id = m.thread_id AND d2.agent_id = ?)`);
|
|
457
457
|
params.push(normalizeAgentId(opts.with_agent));
|
|
458
458
|
}
|
|
@@ -465,20 +465,20 @@ export class MessageStore extends EventEmitter {
|
|
|
465
465
|
params.push(like, like, like);
|
|
466
466
|
}
|
|
467
467
|
const rows = this.db
|
|
468
|
-
.prepare(`SELECT m.thread_id,
|
|
469
|
-
t.title, t.description, t.named_by, t.created_at, t.updated_at,
|
|
470
|
-
(SELECT r.subject FROM messages r WHERE r.thread_id = m.thread_id
|
|
471
|
-
ORDER BY r.created_at ASC, r.id ASC LIMIT 1) AS subject,
|
|
472
|
-
COUNT(DISTINCT m.id) AS messages,
|
|
473
|
-
COUNT(DISTINCT CASE WHEN d.read_at IS NULL AND d.role != 'sender' THEN m.id END) AS unread,
|
|
474
|
-
MAX(m.created_at) AS last_message_at
|
|
475
|
-
FROM deliveries d
|
|
476
|
-
JOIN messages m ON m.id = d.message_id
|
|
477
|
-
${THREAD_JOIN}
|
|
478
|
-
WHERE ${where.join(" AND ")}
|
|
479
|
-
GROUP BY m.thread_id
|
|
480
|
-
${having.length ? `HAVING ${having.join(" AND ")}` : ""}
|
|
481
|
-
ORDER BY last_message_at DESC
|
|
468
|
+
.prepare(`SELECT m.thread_id,
|
|
469
|
+
t.title, t.description, t.named_by, t.created_at, t.updated_at,
|
|
470
|
+
(SELECT r.subject FROM messages r WHERE r.thread_id = m.thread_id
|
|
471
|
+
ORDER BY r.created_at ASC, r.id ASC LIMIT 1) AS subject,
|
|
472
|
+
COUNT(DISTINCT m.id) AS messages,
|
|
473
|
+
COUNT(DISTINCT CASE WHEN d.read_at IS NULL AND d.role != 'sender' THEN m.id END) AS unread,
|
|
474
|
+
MAX(m.created_at) AS last_message_at
|
|
475
|
+
FROM deliveries d
|
|
476
|
+
JOIN messages m ON m.id = d.message_id
|
|
477
|
+
${THREAD_JOIN}
|
|
478
|
+
WHERE ${where.join(" AND ")}
|
|
479
|
+
GROUP BY m.thread_id
|
|
480
|
+
${having.length ? `HAVING ${having.join(" AND ")}` : ""}
|
|
481
|
+
ORDER BY last_message_at DESC
|
|
482
482
|
LIMIT ? OFFSET ?`)
|
|
483
483
|
.all(...params, limit, offset);
|
|
484
484
|
const participants = this.participantsFor(rows.map((r) => r.thread_id));
|
|
@@ -501,18 +501,18 @@ export class MessageStore extends EventEmitter {
|
|
|
501
501
|
/** Écriture brute du nom : un champ absent (`null`) laisse l'ancienne valeur en place. */
|
|
502
502
|
renameThreadRow(threadId, agentId, title, description, now) {
|
|
503
503
|
this.db
|
|
504
|
-
.prepare(`INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
505
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
506
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
507
|
-
title = COALESCE(excluded.title, threads.title),
|
|
508
|
-
description = COALESCE(excluded.description, threads.description),
|
|
509
|
-
named_by = excluded.named_by,
|
|
504
|
+
.prepare(`INSERT INTO threads (id, title, description, named_by, created_at, updated_at)
|
|
505
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
506
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
507
|
+
title = COALESCE(excluded.title, threads.title),
|
|
508
|
+
description = COALESCE(excluded.description, threads.description),
|
|
509
|
+
named_by = excluded.named_by,
|
|
510
510
|
updated_at = excluded.updated_at`)
|
|
511
511
|
.run(threadId, title, description, agentId, now, now);
|
|
512
512
|
}
|
|
513
513
|
assertThreadVisible(agentId, threadId) {
|
|
514
514
|
const row = this.db
|
|
515
|
-
.prepare(`SELECT 1 FROM deliveries d JOIN messages m ON m.id = d.message_id
|
|
515
|
+
.prepare(`SELECT 1 FROM deliveries d JOIN messages m ON m.id = d.message_id
|
|
516
516
|
WHERE d.agent_id = ? AND m.thread_id = ? LIMIT 1`)
|
|
517
517
|
.get(agentId, threadId);
|
|
518
518
|
if (!row)
|
|
@@ -525,9 +525,9 @@ export class MessageStore extends EventEmitter {
|
|
|
525
525
|
return map;
|
|
526
526
|
const placeholders = ids.map(() => "?").join(",");
|
|
527
527
|
const rows = this.db
|
|
528
|
-
.prepare(`SELECT DISTINCT m.thread_id, d.agent_id
|
|
529
|
-
FROM deliveries d JOIN messages m ON m.id = d.message_id
|
|
530
|
-
WHERE m.thread_id IN (${placeholders})
|
|
528
|
+
.prepare(`SELECT DISTINCT m.thread_id, d.agent_id
|
|
529
|
+
FROM deliveries d JOIN messages m ON m.id = d.message_id
|
|
530
|
+
WHERE m.thread_id IN (${placeholders})
|
|
531
531
|
ORDER BY d.agent_id`)
|
|
532
532
|
.all(...ids);
|
|
533
533
|
for (const row of rows) {
|
|
@@ -573,7 +573,7 @@ export class MessageStore extends EventEmitter {
|
|
|
573
573
|
listFolders(agentId) {
|
|
574
574
|
const custom = this.db.prepare("SELECT name FROM folders WHERE agent_id = ? ORDER BY name").all(agentId);
|
|
575
575
|
const counts = this.db
|
|
576
|
-
.prepare(`SELECT folder, COUNT(*) AS total, SUM(CASE WHEN read_at IS NULL THEN 1 ELSE 0 END) AS unread
|
|
576
|
+
.prepare(`SELECT folder, COUNT(*) AS total, SUM(CASE WHEN read_at IS NULL THEN 1 ELSE 0 END) AS unread
|
|
577
577
|
FROM deliveries WHERE agent_id = ? GROUP BY folder`)
|
|
578
578
|
.all(agentId);
|
|
579
579
|
const byName = new Map(counts.map((c) => [c.folder, c]));
|
|
@@ -612,7 +612,7 @@ export class MessageStore extends EventEmitter {
|
|
|
612
612
|
if (!exists)
|
|
613
613
|
throw new MessengerError(`folder not found: ${folder}`, 404);
|
|
614
614
|
const moved = this.db
|
|
615
|
-
.prepare(`UPDATE deliveries SET folder = CASE role WHEN 'sender' THEN 'sent' ELSE 'inbox' END
|
|
615
|
+
.prepare(`UPDATE deliveries SET folder = CASE role WHEN 'sender' THEN 'sent' ELSE 'inbox' END
|
|
616
616
|
WHERE agent_id = ? AND folder = ?`)
|
|
617
617
|
.run(agentId, folder).changes;
|
|
618
618
|
this.db.prepare("DELETE FROM folders WHERE agent_id = ? AND name = ?").run(agentId, folder);
|
|
@@ -645,10 +645,10 @@ export class MessageStore extends EventEmitter {
|
|
|
645
645
|
*/
|
|
646
646
|
staleMailboxes(before) {
|
|
647
647
|
const rows = this.db
|
|
648
|
-
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at,
|
|
649
|
-
(SELECT COUNT(*) FROM deliveries d WHERE d.agent_id = a.id) AS messages
|
|
650
|
-
FROM agents a
|
|
651
|
-
WHERE MAX(${LAST_ACTIVITY}, a.created_at) < ?
|
|
648
|
+
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at,
|
|
649
|
+
(SELECT COUNT(*) FROM deliveries d WHERE d.agent_id = a.id) AS messages
|
|
650
|
+
FROM agents a
|
|
651
|
+
WHERE MAX(${LAST_ACTIVITY}, a.created_at) < ?
|
|
652
652
|
ORDER BY MAX(${LAST_ACTIVITY}, a.created_at) ASC`)
|
|
653
653
|
.all(before);
|
|
654
654
|
return rows.map((row) => ({
|
|
@@ -667,10 +667,10 @@ export class MessageStore extends EventEmitter {
|
|
|
667
667
|
*/
|
|
668
668
|
mailboxesCreatedBefore(before) {
|
|
669
669
|
const rows = this.db
|
|
670
|
-
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at,
|
|
671
|
-
(SELECT COUNT(*) FROM deliveries d WHERE d.agent_id = a.id) AS messages
|
|
672
|
-
FROM agents a
|
|
673
|
-
WHERE a.created_at < ?
|
|
670
|
+
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at,
|
|
671
|
+
(SELECT COUNT(*) FROM deliveries d WHERE d.agent_id = a.id) AS messages
|
|
672
|
+
FROM agents a
|
|
673
|
+
WHERE a.created_at < ?
|
|
674
674
|
ORDER BY a.created_at ASC`)
|
|
675
675
|
.all(before);
|
|
676
676
|
return rows.map((row) => ({
|
|
@@ -689,9 +689,9 @@ export class MessageStore extends EventEmitter {
|
|
|
689
689
|
*/
|
|
690
690
|
emptyMailboxes() {
|
|
691
691
|
const rows = this.db
|
|
692
|
-
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at, 0 AS messages
|
|
693
|
-
FROM agents a
|
|
694
|
-
WHERE NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.agent_id = a.id)
|
|
692
|
+
.prepare(`SELECT a.*, ${LAST_ACTIVITY} AS last_activity_at, 0 AS messages
|
|
693
|
+
FROM agents a
|
|
694
|
+
WHERE NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.agent_id = a.id)
|
|
695
695
|
ORDER BY a.created_at ASC`)
|
|
696
696
|
.all();
|
|
697
697
|
return rows.map((row) => ({
|
|
@@ -828,12 +828,12 @@ export class MessageStore extends EventEmitter {
|
|
|
828
828
|
params.push(opts.thread_id);
|
|
829
829
|
}
|
|
830
830
|
const rows = this.db
|
|
831
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
832
|
-
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
833
|
-
FROM messages m
|
|
834
|
-
${THREAD_JOIN}
|
|
835
|
-
WHERE ${where.join(" AND ")}
|
|
836
|
-
ORDER BY m.created_at DESC, m.id DESC
|
|
831
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
832
|
+
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
833
|
+
FROM messages m
|
|
834
|
+
${THREAD_JOIN}
|
|
835
|
+
WHERE ${where.join(" AND ")}
|
|
836
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
837
837
|
LIMIT ? OFFSET ?`)
|
|
838
838
|
.all(...params, limit, offset);
|
|
839
839
|
const related = this.relatedFor(rows.map((r) => r.message_id));
|
|
@@ -856,7 +856,7 @@ export class MessageStore extends EventEmitter {
|
|
|
856
856
|
// reçu : c'est la correspondance qui est filtrée, pas les messages qu'elle a signés.
|
|
857
857
|
const agents = dedupe((opts.agents ?? []).map((id) => normalizeAgentId(id)).filter(Boolean));
|
|
858
858
|
if (agents.length > 0) {
|
|
859
|
-
where.push(`EXISTS (SELECT 1 FROM deliveries d JOIN messages mp ON mp.id = d.message_id
|
|
859
|
+
where.push(`EXISTS (SELECT 1 FROM deliveries d JOIN messages mp ON mp.id = d.message_id
|
|
860
860
|
WHERE mp.thread_id = m.thread_id AND d.agent_id IN (${agents.map(() => "?").join(",")}))`);
|
|
861
861
|
params.push(...agents);
|
|
862
862
|
}
|
|
@@ -864,8 +864,8 @@ export class MessageStore extends EventEmitter {
|
|
|
864
864
|
// n'importe lequel de ses messages. Un fil non nommé reste ainsi trouvable.
|
|
865
865
|
if (opts.search) {
|
|
866
866
|
const like = `%${escapeLike(opts.search)}%`;
|
|
867
|
-
where.push(`(COALESCE(t.title, '') LIKE ? ESCAPE '\\' OR COALESCE(t.description, '') LIKE ? ESCAPE '\\'
|
|
868
|
-
OR EXISTS (SELECT 1 FROM messages ms WHERE ms.thread_id = m.thread_id
|
|
867
|
+
where.push(`(COALESCE(t.title, '') LIKE ? ESCAPE '\\' OR COALESCE(t.description, '') LIKE ? ESCAPE '\\'
|
|
868
|
+
OR EXISTS (SELECT 1 FROM messages ms WHERE ms.thread_id = m.thread_id
|
|
869
869
|
AND (ms.subject LIKE ? ESCAPE '\\' OR ms.body LIKE ? ESCAPE '\\')))`);
|
|
870
870
|
params.push(like, like, like, like);
|
|
871
871
|
}
|
|
@@ -874,28 +874,28 @@ export class MessageStore extends EventEmitter {
|
|
|
874
874
|
if (opts.priority)
|
|
875
875
|
having.push("top_priority = ?");
|
|
876
876
|
const rows = this.db
|
|
877
|
-
.prepare(`SELECT m.thread_id,
|
|
878
|
-
t.title, t.description, t.named_by, t.created_at, t.updated_at,
|
|
879
|
-
(SELECT r.subject FROM messages r WHERE r.thread_id = m.thread_id
|
|
880
|
-
ORDER BY r.created_at ASC, r.id ASC LIMIT 1) AS subject,
|
|
881
|
-
COUNT(*) AS messages,
|
|
882
|
-
MAX(m.created_at) AS last_message_at,
|
|
883
|
-
(SELECT r.sender_id FROM messages r WHERE r.thread_id = m.thread_id
|
|
884
|
-
ORDER BY r.created_at DESC, r.id DESC LIMIT 1) AS last_message_from,
|
|
885
|
-
(SELECT r.body FROM messages r WHERE r.thread_id = m.thread_id
|
|
886
|
-
ORDER BY r.created_at DESC, r.id DESC LIMIT 1) AS last_body,
|
|
887
|
-
(SELECT COUNT(*) FROM deliveries d JOIN messages mu ON mu.id = d.message_id
|
|
888
|
-
WHERE mu.thread_id = m.thread_id AND d.role != 'sender'
|
|
889
|
-
AND d.read_at IS NULL AND d.folder != 'trash') AS unread,
|
|
890
|
-
CASE WHEN SUM(m.priority = 'high') > 0 THEN 'high'
|
|
891
|
-
WHEN SUM(m.priority = 'normal') > 0 THEN 'normal'
|
|
892
|
-
ELSE 'low' END AS top_priority
|
|
893
|
-
FROM messages m
|
|
894
|
-
${THREAD_JOIN}
|
|
895
|
-
WHERE ${where.join(" AND ")}
|
|
896
|
-
GROUP BY m.thread_id
|
|
897
|
-
${having.length ? `HAVING ${having.join(" AND ")}` : ""}
|
|
898
|
-
ORDER BY last_message_at DESC
|
|
877
|
+
.prepare(`SELECT m.thread_id,
|
|
878
|
+
t.title, t.description, t.named_by, t.created_at, t.updated_at,
|
|
879
|
+
(SELECT r.subject FROM messages r WHERE r.thread_id = m.thread_id
|
|
880
|
+
ORDER BY r.created_at ASC, r.id ASC LIMIT 1) AS subject,
|
|
881
|
+
COUNT(*) AS messages,
|
|
882
|
+
MAX(m.created_at) AS last_message_at,
|
|
883
|
+
(SELECT r.sender_id FROM messages r WHERE r.thread_id = m.thread_id
|
|
884
|
+
ORDER BY r.created_at DESC, r.id DESC LIMIT 1) AS last_message_from,
|
|
885
|
+
(SELECT r.body FROM messages r WHERE r.thread_id = m.thread_id
|
|
886
|
+
ORDER BY r.created_at DESC, r.id DESC LIMIT 1) AS last_body,
|
|
887
|
+
(SELECT COUNT(*) FROM deliveries d JOIN messages mu ON mu.id = d.message_id
|
|
888
|
+
WHERE mu.thread_id = m.thread_id AND d.role != 'sender'
|
|
889
|
+
AND d.read_at IS NULL AND d.folder != 'trash') AS unread,
|
|
890
|
+
CASE WHEN SUM(m.priority = 'high') > 0 THEN 'high'
|
|
891
|
+
WHEN SUM(m.priority = 'normal') > 0 THEN 'normal'
|
|
892
|
+
ELSE 'low' END AS top_priority
|
|
893
|
+
FROM messages m
|
|
894
|
+
${THREAD_JOIN}
|
|
895
|
+
WHERE ${where.join(" AND ")}
|
|
896
|
+
GROUP BY m.thread_id
|
|
897
|
+
${having.length ? `HAVING ${having.join(" AND ")}` : ""}
|
|
898
|
+
ORDER BY last_message_at DESC
|
|
899
899
|
LIMIT ? OFFSET ?`)
|
|
900
900
|
.all(...params, ...(opts.priority ? [opts.priority] : []), limit, offset);
|
|
901
901
|
const participants = this.participantsFor(rows.map((r) => r.thread_id));
|
|
@@ -925,11 +925,11 @@ export class MessageStore extends EventEmitter {
|
|
|
925
925
|
*/
|
|
926
926
|
listThreadAdmin(threadId) {
|
|
927
927
|
const rows = this.db
|
|
928
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
929
|
-
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
930
|
-
FROM messages m
|
|
931
|
-
${THREAD_JOIN}
|
|
932
|
-
WHERE m.thread_id = ?
|
|
928
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
929
|
+
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
930
|
+
FROM messages m
|
|
931
|
+
${THREAD_JOIN}
|
|
932
|
+
WHERE m.thread_id = ?
|
|
933
933
|
ORDER BY m.created_at ASC, m.id ASC`)
|
|
934
934
|
.all(threadId);
|
|
935
935
|
const related = this.relatedFor(rows.map((r) => r.message_id));
|
|
@@ -938,8 +938,8 @@ export class MessageStore extends EventEmitter {
|
|
|
938
938
|
/** Message complet vu du serveur, avec l'état de lecture de chaque destinataire. */
|
|
939
939
|
getMessageAdmin(messageId) {
|
|
940
940
|
const row = this.db
|
|
941
|
-
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
942
|
-
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
941
|
+
.prepare(`SELECT m.id AS message_id, m.thread_id, t.title AS thread_title, m.sender_id, m.subject, m.body,
|
|
942
|
+
m.priority, m.in_reply_to, m.created_at, 0 AS starred, ${ADMIN_DELIVERY}
|
|
943
943
|
FROM messages m ${THREAD_JOIN} WHERE m.id = ?`)
|
|
944
944
|
.get(messageId);
|
|
945
945
|
if (!row)
|
|
@@ -961,7 +961,7 @@ export class MessageStore extends EventEmitter {
|
|
|
961
961
|
/** Pièce jointe vue du serveur, quelle que soit la boîte. */
|
|
962
962
|
getAttachmentAdmin(attachmentId) {
|
|
963
963
|
const row = this.db
|
|
964
|
-
.prepare(`SELECT id AS attachment_id, message_id, filename, mime_type, size, data
|
|
964
|
+
.prepare(`SELECT id AS attachment_id, message_id, filename, mime_type, size, data
|
|
965
965
|
FROM attachments WHERE id = ?`)
|
|
966
966
|
.get(attachmentId);
|
|
967
967
|
if (!row)
|
|
@@ -973,9 +973,9 @@ export class MessageStore extends EventEmitter {
|
|
|
973
973
|
const threads = this.db.prepare("SELECT COUNT(DISTINCT thread_id) AS n FROM messages").get().n;
|
|
974
974
|
const sentByAgent = new Map(this.db.prepare("SELECT sender_id, COUNT(*) AS n FROM messages GROUP BY sender_id").all().map((r) => [r.sender_id, r.n]));
|
|
975
975
|
const receivedByAgent = new Map(this.db
|
|
976
|
-
.prepare(`SELECT agent_id,
|
|
977
|
-
COUNT(*) AS received,
|
|
978
|
-
SUM(CASE WHEN read_at IS NULL AND folder != 'trash' THEN 1 ELSE 0 END) AS unread
|
|
976
|
+
.prepare(`SELECT agent_id,
|
|
977
|
+
COUNT(*) AS received,
|
|
978
|
+
SUM(CASE WHEN read_at IS NULL AND folder != 'trash' THEN 1 ELSE 0 END) AS unread
|
|
979
979
|
FROM deliveries WHERE role != 'sender' GROUP BY agent_id`)
|
|
980
980
|
.all().map((r) => [r.agent_id, r]));
|
|
981
981
|
const agents = this.listAgents().map((agent) => ({
|
|
@@ -1022,14 +1022,14 @@ export class MessageStore extends EventEmitter {
|
|
|
1022
1022
|
return related;
|
|
1023
1023
|
const placeholders = ids.map(() => "?").join(",");
|
|
1024
1024
|
const recipients = this.db
|
|
1025
|
-
.prepare(`SELECT message_id, agent_id FROM deliveries
|
|
1025
|
+
.prepare(`SELECT message_id, agent_id FROM deliveries
|
|
1026
1026
|
WHERE message_id IN (${placeholders}) AND role = 'to'`)
|
|
1027
1027
|
.all(...ids);
|
|
1028
1028
|
for (const row of recipients)
|
|
1029
1029
|
related.recipients.set(row.message_id, row.agent_id);
|
|
1030
1030
|
const attachments = this.db
|
|
1031
|
-
.prepare(`SELECT id AS attachment_id, message_id, filename, mime_type, size FROM attachments
|
|
1032
|
-
WHERE message_id IN (${placeholders})
|
|
1031
|
+
.prepare(`SELECT id AS attachment_id, message_id, filename, mime_type, size FROM attachments
|
|
1032
|
+
WHERE message_id IN (${placeholders})
|
|
1033
1033
|
ORDER BY message_id, position`)
|
|
1034
1034
|
.all(...ids);
|
|
1035
1035
|
for (const { message_id, ...info } of attachments) {
|