@kernhq/module-quire 0.3.0 → 0.4.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/dist/contract/events.d.ts +5 -0
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +1 -0
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/models.d.ts +82 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +41 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/notifications.d.ts +9 -0
- package/dist/contract/notifications.d.ts.map +1 -0
- package/dist/contract/notifications.js +16 -0
- package/dist/contract/notifications.js.map +1 -0
- package/dist/contract/permissions.d.ts +7 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +8 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +270 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +33 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +303 -0
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +55 -0
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +132 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema.d.ts +335 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +44 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/comments.d.ts +172 -0
- package/dist/server/services/comments.d.ts.map +1 -0
- package/dist/server/services/comments.js +182 -0
- package/dist/server/services/comments.js.map +1 -0
- package/dist/server/services/index.d.ts +3 -0
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +3 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/notify.d.ts +24 -0
- package/dist/server/services/notify.d.ts.map +1 -0
- package/dist/server/services/notify.js +54 -0
- package/dist/server/services/notify.js.map +1 -0
- package/migrations/0003_comments.sql +30 -0
- package/migrations/meta/0003_snapshot.json +668 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/index.ts +2 -0
- package/src/contract/events.ts +4 -0
- package/src/contract/index.ts +1 -0
- package/src/contract/models.ts +48 -0
- package/src/contract/notifications.ts +17 -0
- package/src/contract/permissions.ts +8 -0
- package/src/contract/router.ts +47 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Principal } from '@kernhq/contracts';
|
|
2
|
+
import { type Tx } from '@kernhq/kernel';
|
|
3
|
+
import type { Comment } from '../../contract/index.js';
|
|
4
|
+
import { comments } from '../schema.js';
|
|
5
|
+
import type { QuireAccess } from './access.js';
|
|
6
|
+
type CommentRow = typeof comments.$inferSelect;
|
|
7
|
+
export declare function toComment(row: CommentRow): Comment;
|
|
8
|
+
/**
|
|
9
|
+
* Flatten a comment body to text, for search and for a notification's one-line preview.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately dumb: it walks whatever the editor produced and takes the `text` leaves. A renderer
|
|
12
|
+
* that understood every node would have to be kept in step with the editor's schema, and the only
|
|
13
|
+
* thing this is used for is a line of plain prose.
|
|
14
|
+
*/
|
|
15
|
+
export declare function flattenBody(body: unknown): string;
|
|
16
|
+
/** Every user id mentioned in the body, so they can be notified without a second parse. */
|
|
17
|
+
export declare function mentionsIn(body: unknown): string[];
|
|
18
|
+
export declare function quireComments(access: QuireAccess): {
|
|
19
|
+
list(tx: Tx, workspaceId: string, pageId: string, includeResolved: boolean): Promise<{
|
|
20
|
+
id: string;
|
|
21
|
+
root: {
|
|
22
|
+
id: string;
|
|
23
|
+
workspaceId: string & import("zod").$brand<"WorkspaceId">;
|
|
24
|
+
pageId: string;
|
|
25
|
+
parentId: string | null;
|
|
26
|
+
threadId: string;
|
|
27
|
+
authorId: (string & import("zod").$brand<"UserId">) | null;
|
|
28
|
+
body: Record<string, unknown>;
|
|
29
|
+
bodyText: string;
|
|
30
|
+
mentionIds: (string & import("zod").$brand<"UserId">)[];
|
|
31
|
+
anchor: {
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
} | null;
|
|
35
|
+
quotedText: string;
|
|
36
|
+
resolvedAt: string | null;
|
|
37
|
+
resolvedBy: (string & import("zod").$brand<"UserId">) | null;
|
|
38
|
+
editedAt: string | null;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
};
|
|
41
|
+
replies: {
|
|
42
|
+
id: string;
|
|
43
|
+
workspaceId: string & import("zod").$brand<"WorkspaceId">;
|
|
44
|
+
pageId: string;
|
|
45
|
+
parentId: string | null;
|
|
46
|
+
threadId: string;
|
|
47
|
+
authorId: (string & import("zod").$brand<"UserId">) | null;
|
|
48
|
+
body: Record<string, unknown>;
|
|
49
|
+
bodyText: string;
|
|
50
|
+
mentionIds: (string & import("zod").$brand<"UserId">)[];
|
|
51
|
+
anchor: {
|
|
52
|
+
from: string;
|
|
53
|
+
to: string;
|
|
54
|
+
} | null;
|
|
55
|
+
quotedText: string;
|
|
56
|
+
resolvedAt: string | null;
|
|
57
|
+
resolvedBy: (string & import("zod").$brand<"UserId">) | null;
|
|
58
|
+
editedAt: string | null;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
}[];
|
|
61
|
+
resolved: boolean;
|
|
62
|
+
}[]>;
|
|
63
|
+
row(tx: Tx, workspaceId: string, commentId: string): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
workspaceId: string;
|
|
66
|
+
pageId: string;
|
|
67
|
+
parentId: string | null;
|
|
68
|
+
threadId: string;
|
|
69
|
+
authorId: string | null;
|
|
70
|
+
body: unknown;
|
|
71
|
+
bodyText: string;
|
|
72
|
+
mentionIds: string[];
|
|
73
|
+
anchor: unknown;
|
|
74
|
+
quotedText: string;
|
|
75
|
+
resolvedAt: Date | null;
|
|
76
|
+
resolvedBy: string | null;
|
|
77
|
+
editedAt: Date | null;
|
|
78
|
+
deletedAt: Date | null;
|
|
79
|
+
createdAt: Date;
|
|
80
|
+
updatedAt: Date;
|
|
81
|
+
}>;
|
|
82
|
+
create(tx: Tx, principal: Principal, workspaceId: string, input: {
|
|
83
|
+
pageId: string;
|
|
84
|
+
body: Record<string, unknown>;
|
|
85
|
+
anchor: {
|
|
86
|
+
from: string;
|
|
87
|
+
to: string;
|
|
88
|
+
} | null;
|
|
89
|
+
quotedText: string;
|
|
90
|
+
parentId: string | null;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
id: string;
|
|
93
|
+
pageId: string;
|
|
94
|
+
workspaceId: string;
|
|
95
|
+
anchor: unknown;
|
|
96
|
+
parentId: string | null;
|
|
97
|
+
createdAt: Date;
|
|
98
|
+
updatedAt: Date;
|
|
99
|
+
deletedAt: Date | null;
|
|
100
|
+
authorId: string | null;
|
|
101
|
+
threadId: string;
|
|
102
|
+
body: unknown;
|
|
103
|
+
bodyText: string;
|
|
104
|
+
mentionIds: string[];
|
|
105
|
+
quotedText: string;
|
|
106
|
+
resolvedAt: Date | null;
|
|
107
|
+
resolvedBy: string | null;
|
|
108
|
+
editedAt: Date | null;
|
|
109
|
+
}>;
|
|
110
|
+
update(tx: Tx, principal: Principal, workspaceId: string, commentId: string, body: Record<string, unknown>): Promise<{
|
|
111
|
+
id: string;
|
|
112
|
+
workspaceId: string;
|
|
113
|
+
pageId: string;
|
|
114
|
+
parentId: string | null;
|
|
115
|
+
threadId: string;
|
|
116
|
+
authorId: string | null;
|
|
117
|
+
body: unknown;
|
|
118
|
+
bodyText: string;
|
|
119
|
+
mentionIds: string[];
|
|
120
|
+
anchor: unknown;
|
|
121
|
+
quotedText: string;
|
|
122
|
+
resolvedAt: Date | null;
|
|
123
|
+
resolvedBy: string | null;
|
|
124
|
+
editedAt: Date | null;
|
|
125
|
+
deletedAt: Date | null;
|
|
126
|
+
createdAt: Date;
|
|
127
|
+
updatedAt: Date;
|
|
128
|
+
}>;
|
|
129
|
+
/** Soft, so a thread does not lose its shape when one remark in the middle goes. */
|
|
130
|
+
remove(tx: Tx, principal: Principal, workspaceId: string, commentId: string): Promise<{
|
|
131
|
+
id: string;
|
|
132
|
+
workspaceId: string;
|
|
133
|
+
pageId: string;
|
|
134
|
+
parentId: string | null;
|
|
135
|
+
threadId: string;
|
|
136
|
+
authorId: string | null;
|
|
137
|
+
body: unknown;
|
|
138
|
+
bodyText: string;
|
|
139
|
+
mentionIds: string[];
|
|
140
|
+
anchor: unknown;
|
|
141
|
+
quotedText: string;
|
|
142
|
+
resolvedAt: Date | null;
|
|
143
|
+
resolvedBy: string | null;
|
|
144
|
+
editedAt: Date | null;
|
|
145
|
+
deletedAt: Date | null;
|
|
146
|
+
createdAt: Date;
|
|
147
|
+
updatedAt: Date;
|
|
148
|
+
}>;
|
|
149
|
+
/** Resolving is a property of the thread, so it is written on the root. */
|
|
150
|
+
resolve(tx: Tx, principal: Principal, workspaceId: string, commentId: string, resolved: boolean): Promise<{
|
|
151
|
+
id: string;
|
|
152
|
+
workspaceId: string;
|
|
153
|
+
pageId: string;
|
|
154
|
+
parentId: string | null;
|
|
155
|
+
threadId: string;
|
|
156
|
+
authorId: string | null;
|
|
157
|
+
body: unknown;
|
|
158
|
+
bodyText: string;
|
|
159
|
+
mentionIds: string[];
|
|
160
|
+
anchor: unknown;
|
|
161
|
+
quotedText: string;
|
|
162
|
+
resolvedAt: Date | null;
|
|
163
|
+
resolvedBy: string | null;
|
|
164
|
+
editedAt: Date | null;
|
|
165
|
+
deletedAt: Date | null;
|
|
166
|
+
createdAt: Date;
|
|
167
|
+
updatedAt: Date;
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
170
|
+
export type QuireComments = ReturnType<typeof quireComments>;
|
|
171
|
+
export {};
|
|
172
|
+
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../../src/server/services/comments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAa,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,yBAAyB,CAAA;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,KAAK,UAAU,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AAE9C,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAkBlD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAUjD;AAED,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAUlD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW;aAE9B,EAAE,eAAe,MAAM,UAAU,MAAM,mBAAmB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAoClE,EAAE,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;;;;eAalD,EAAE,aACK,SAAS,eACP,MAAM,SACZ;QACL,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAC7B,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;QAC3C,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KACxB;;;;;;;;;;;;;;;;;;;eAkCG,EAAE,aACK,SAAS,eACP,MAAM,aACR,MAAM,QACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;;;;;;;;IAmB/B,oFAAoF;eACnE,EAAE,aAAa,SAAS,eAAe,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;;;;IAajF,2EAA2E;gBACzD,EAAE,aAAa,SAAS,eAAe,MAAM,aAAa,MAAM,YAAY,OAAO;;;;;;;;;;;;;;;;;;;EAaxG;AACD,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { KernError, uuidv7 } from '@kernhq/kernel';
|
|
2
|
+
import { and, asc, eq, isNull } from 'drizzle-orm';
|
|
3
|
+
import { comments } from '../schema.js';
|
|
4
|
+
export function toComment(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
workspaceId: row.workspaceId,
|
|
8
|
+
pageId: row.pageId,
|
|
9
|
+
parentId: row.parentId,
|
|
10
|
+
threadId: row.threadId,
|
|
11
|
+
authorId: row.authorId,
|
|
12
|
+
body: row.body,
|
|
13
|
+
bodyText: row.bodyText,
|
|
14
|
+
mentionIds: row.mentionIds,
|
|
15
|
+
anchor: row.anchor,
|
|
16
|
+
quotedText: row.quotedText,
|
|
17
|
+
resolvedAt: row.resolvedAt?.toISOString() ?? null,
|
|
18
|
+
resolvedBy: row.resolvedBy,
|
|
19
|
+
editedAt: row.editedAt?.toISOString() ?? null,
|
|
20
|
+
createdAt: row.createdAt.toISOString(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Flatten a comment body to text, for search and for a notification's one-line preview.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately dumb: it walks whatever the editor produced and takes the `text` leaves. A renderer
|
|
27
|
+
* that understood every node would have to be kept in step with the editor's schema, and the only
|
|
28
|
+
* thing this is used for is a line of plain prose.
|
|
29
|
+
*/
|
|
30
|
+
export function flattenBody(body) {
|
|
31
|
+
const out = [];
|
|
32
|
+
const walk = (node) => {
|
|
33
|
+
if (!node || typeof node !== 'object')
|
|
34
|
+
return;
|
|
35
|
+
const n = node;
|
|
36
|
+
if (typeof n.text === 'string')
|
|
37
|
+
out.push(n.text);
|
|
38
|
+
if (Array.isArray(n.content))
|
|
39
|
+
for (const child of n.content)
|
|
40
|
+
walk(child);
|
|
41
|
+
};
|
|
42
|
+
walk(body);
|
|
43
|
+
return out.join(' ').replace(/\s+/g, ' ').trim();
|
|
44
|
+
}
|
|
45
|
+
/** Every user id mentioned in the body, so they can be notified without a second parse. */
|
|
46
|
+
export function mentionsIn(body) {
|
|
47
|
+
const ids = new Set();
|
|
48
|
+
const walk = (node) => {
|
|
49
|
+
if (!node || typeof node !== 'object')
|
|
50
|
+
return;
|
|
51
|
+
const n = node;
|
|
52
|
+
if (n.type === 'mention' && typeof n.attrs?.id === 'string')
|
|
53
|
+
ids.add(n.attrs.id);
|
|
54
|
+
if (Array.isArray(n.content))
|
|
55
|
+
for (const child of n.content)
|
|
56
|
+
walk(child);
|
|
57
|
+
};
|
|
58
|
+
walk(body);
|
|
59
|
+
return [...ids];
|
|
60
|
+
}
|
|
61
|
+
export function quireComments(access) {
|
|
62
|
+
return {
|
|
63
|
+
async list(tx, workspaceId, pageId, includeResolved) {
|
|
64
|
+
await access.pageRow(tx, workspaceId, pageId);
|
|
65
|
+
const rows = await tx
|
|
66
|
+
.select()
|
|
67
|
+
.from(comments)
|
|
68
|
+
.where(and(eq(comments.workspaceId, workspaceId), eq(comments.pageId, pageId), isNull(comments.deletedAt)))
|
|
69
|
+
.orderBy(asc(comments.createdAt));
|
|
70
|
+
const byThread = new Map();
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
const list = byThread.get(row.threadId) ?? [];
|
|
73
|
+
list.push(row);
|
|
74
|
+
byThread.set(row.threadId, list);
|
|
75
|
+
}
|
|
76
|
+
const threads = [];
|
|
77
|
+
for (const [threadId, list] of byThread) {
|
|
78
|
+
const root = list.find((r) => r.id === threadId);
|
|
79
|
+
// A thread whose root was deleted while replies remain: the replies are still somebody's
|
|
80
|
+
// words, so the thread is kept and the oldest remaining comment leads it.
|
|
81
|
+
const lead = root ?? list[0];
|
|
82
|
+
if (!lead)
|
|
83
|
+
continue;
|
|
84
|
+
const resolved = Boolean(lead.resolvedAt);
|
|
85
|
+
if (resolved && !includeResolved)
|
|
86
|
+
continue;
|
|
87
|
+
threads.push({
|
|
88
|
+
id: threadId,
|
|
89
|
+
root: toComment(lead),
|
|
90
|
+
replies: list.filter((r) => r.id !== lead.id).map(toComment),
|
|
91
|
+
resolved,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return threads;
|
|
95
|
+
},
|
|
96
|
+
async row(tx, workspaceId, commentId) {
|
|
97
|
+
const [row] = await tx
|
|
98
|
+
.select()
|
|
99
|
+
.from(comments)
|
|
100
|
+
.where(and(eq(comments.workspaceId, workspaceId), eq(comments.id, commentId), isNull(comments.deletedAt)))
|
|
101
|
+
.limit(1);
|
|
102
|
+
if (!row)
|
|
103
|
+
throw KernError.notFound('Comment');
|
|
104
|
+
return row;
|
|
105
|
+
},
|
|
106
|
+
async create(tx, principal, workspaceId, input) {
|
|
107
|
+
await access.pageRow(tx, workspaceId, input.pageId);
|
|
108
|
+
let threadId = uuidv7();
|
|
109
|
+
if (input.parentId) {
|
|
110
|
+
const parent = await this.row(tx, workspaceId, input.parentId);
|
|
111
|
+
if (parent.pageId !== input.pageId)
|
|
112
|
+
throw KernError.badRequest('That comment is on a different page');
|
|
113
|
+
// Replying to a reply joins the thread rather than nesting further: a margin note is a
|
|
114
|
+
// conversation, and arbitrary depth in a 320px column is unreadable.
|
|
115
|
+
threadId = parent.threadId;
|
|
116
|
+
}
|
|
117
|
+
const id = uuidv7();
|
|
118
|
+
const [row] = await tx
|
|
119
|
+
.insert(comments)
|
|
120
|
+
.values({
|
|
121
|
+
id,
|
|
122
|
+
workspaceId,
|
|
123
|
+
pageId: input.pageId,
|
|
124
|
+
parentId: input.parentId,
|
|
125
|
+
threadId: input.parentId ? threadId : id,
|
|
126
|
+
authorId: principal.userId,
|
|
127
|
+
body: input.body,
|
|
128
|
+
bodyText: flattenBody(input.body),
|
|
129
|
+
mentionIds: mentionsIn(input.body),
|
|
130
|
+
anchor: input.anchor,
|
|
131
|
+
quotedText: input.quotedText,
|
|
132
|
+
})
|
|
133
|
+
.returning();
|
|
134
|
+
return row;
|
|
135
|
+
},
|
|
136
|
+
async update(tx, principal, workspaceId, commentId, body) {
|
|
137
|
+
const row = await this.row(tx, workspaceId, commentId);
|
|
138
|
+
// Editing somebody else's words is not a permission anybody should hold.
|
|
139
|
+
if (row.authorId !== principal.userId)
|
|
140
|
+
throw KernError.forbidden('quire.page.comment');
|
|
141
|
+
const [updated] = await tx
|
|
142
|
+
.update(comments)
|
|
143
|
+
.set({
|
|
144
|
+
body,
|
|
145
|
+
bodyText: flattenBody(body),
|
|
146
|
+
mentionIds: mentionsIn(body),
|
|
147
|
+
editedAt: new Date(),
|
|
148
|
+
updatedAt: new Date(),
|
|
149
|
+
})
|
|
150
|
+
.where(and(eq(comments.workspaceId, workspaceId), eq(comments.id, commentId)))
|
|
151
|
+
.returning();
|
|
152
|
+
return updated;
|
|
153
|
+
},
|
|
154
|
+
/** Soft, so a thread does not lose its shape when one remark in the middle goes. */
|
|
155
|
+
async remove(tx, principal, workspaceId, commentId) {
|
|
156
|
+
const row = await this.row(tx, workspaceId, commentId);
|
|
157
|
+
if (row.authorId !== principal.userId && !principal.instanceAdmin) {
|
|
158
|
+
const scope = await access.scopeOf(tx, workspaceId, row.pageId);
|
|
159
|
+
await access.requirePage(principal, 'quire.page.manage', workspaceId, scope);
|
|
160
|
+
}
|
|
161
|
+
await tx
|
|
162
|
+
.update(comments)
|
|
163
|
+
.set({ deletedAt: new Date(), updatedAt: new Date() })
|
|
164
|
+
.where(and(eq(comments.workspaceId, workspaceId), eq(comments.id, commentId)));
|
|
165
|
+
return row;
|
|
166
|
+
},
|
|
167
|
+
/** Resolving is a property of the thread, so it is written on the root. */
|
|
168
|
+
async resolve(tx, principal, workspaceId, commentId, resolved) {
|
|
169
|
+
const row = await this.row(tx, workspaceId, commentId);
|
|
170
|
+
await tx
|
|
171
|
+
.update(comments)
|
|
172
|
+
.set({
|
|
173
|
+
resolvedAt: resolved ? new Date() : null,
|
|
174
|
+
resolvedBy: resolved ? principal.userId : null,
|
|
175
|
+
updatedAt: new Date(),
|
|
176
|
+
})
|
|
177
|
+
.where(and(eq(comments.workspaceId, workspaceId), eq(comments.id, row.threadId)));
|
|
178
|
+
return row;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../../../src/server/services/comments.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Kernel } from '@kernhq/kernel';
|
|
2
2
|
import { quireAccess } from './access.js';
|
|
3
|
+
import { quireComments } from './comments.js';
|
|
3
4
|
import { quirePages } from './pages.js';
|
|
4
5
|
import { quireSpaces } from './spaces.js';
|
|
5
6
|
import { quireVersions } from './versions.js';
|
|
@@ -8,10 +9,12 @@ export interface QuireServices {
|
|
|
8
9
|
spaces: ReturnType<typeof quireSpaces>;
|
|
9
10
|
pages: ReturnType<typeof quirePages>;
|
|
10
11
|
versions: ReturnType<typeof quireVersions>;
|
|
12
|
+
comments: ReturnType<typeof quireComments>;
|
|
11
13
|
}
|
|
12
14
|
/** One set of services per kernel: they are stateless, and rebuilding them per request is waste. */
|
|
13
15
|
export declare function quireServices(kernel: Kernel): QuireServices;
|
|
14
16
|
export * from './access.js';
|
|
17
|
+
export * from './comments.js';
|
|
15
18
|
export * from './pages.js';
|
|
16
19
|
export * from './spaces.js';
|
|
17
20
|
export * from './versions.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACpC,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;CAC3C;AAID,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;IACtC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACpC,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;IAC1C,QAAQ,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;CAC3C;AAID,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAa3D;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { quireAccess } from './access.js';
|
|
2
|
+
import { quireComments } from './comments.js';
|
|
2
3
|
import { quirePages } from './pages.js';
|
|
3
4
|
import { quireSpaces } from './spaces.js';
|
|
4
5
|
import { quireVersions } from './versions.js';
|
|
@@ -14,11 +15,13 @@ export function quireServices(kernel) {
|
|
|
14
15
|
spaces: quireSpaces(access),
|
|
15
16
|
pages: quirePages(access),
|
|
16
17
|
versions: quireVersions(kernel, access),
|
|
18
|
+
comments: quireComments(access),
|
|
17
19
|
};
|
|
18
20
|
cache.set(kernel, services);
|
|
19
21
|
return services;
|
|
20
22
|
}
|
|
21
23
|
export * from './access.js';
|
|
24
|
+
export * from './comments.js';
|
|
22
25
|
export * from './pages.js';
|
|
23
26
|
export * from './spaces.js';
|
|
24
27
|
export * from './versions.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAU7C,MAAM,KAAK,GAAG,IAAI,OAAO,EAAyB,CAAA;AAElD,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAkB;QAC9B,MAAM;QACN,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC;QACzB,QAAQ,EAAE,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC;QACvC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC;KAChC,CAAA;IACD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC3B,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { core } from '@kernhq/contracts';
|
|
2
|
+
import type { Kernel } from '@kernhq/kernel';
|
|
3
|
+
import type { comments } from '../schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* Everything Quire tells the rest of the platform about, and nothing it needs an answer from.
|
|
6
|
+
*
|
|
7
|
+
* Every call here is best effort. A page must not fail to save, and a comment must not fail to
|
|
8
|
+
* post, because core is briefly unavailable — the module's own row is the authoritative record and
|
|
9
|
+
* a missing notification is a smaller loss than a refused write. Failures are logged, not raised.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createNotify(kernel: Kernel): {
|
|
12
|
+
/**
|
|
13
|
+
* Tell everyone named in a comment, except whoever wrote it.
|
|
14
|
+
*
|
|
15
|
+
* Notifying somebody that they mentioned themselves is noise, and it is the commonest way an
|
|
16
|
+
* inbox stops being read.
|
|
17
|
+
*/
|
|
18
|
+
mentions(workspaceId: string, comment: typeof comments.$inferSelect, actorId: string | null): Promise<void>;
|
|
19
|
+
/** Put a page in the workspace search index. */
|
|
20
|
+
index(documents: core.SearchDocument[]): Promise<void>;
|
|
21
|
+
unindex(workspaceId: string, type: string, ids: string[]): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export type QuireNotify = ReturnType<typeof createNotify>;
|
|
24
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../../src/server/services/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE5C;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;IAWvC;;;;;OAKG;0BACyB,MAAM,WAAW,OAAO,QAAQ,CAAC,YAAY,WAAW,MAAM,GAAG,IAAI;IAoBjG,gDAAgD;qBACzB,IAAI,CAAC,cAAc,EAAE;yBAKjB,MAAM,QAAQ,MAAM,OAAO,MAAM,EAAE;EASjE;AACD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything Quire tells the rest of the platform about, and nothing it needs an answer from.
|
|
3
|
+
*
|
|
4
|
+
* Every call here is best effort. A page must not fail to save, and a comment must not fail to
|
|
5
|
+
* post, because core is briefly unavailable — the module's own row is the authoritative record and
|
|
6
|
+
* a missing notification is a smaller loss than a refused write. Failures are logged, not raised.
|
|
7
|
+
*/
|
|
8
|
+
export function createNotify(kernel) {
|
|
9
|
+
const best = async (what, fn) => {
|
|
10
|
+
try {
|
|
11
|
+
return await fn();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
kernel.log.warn({ err: String(err), what }, 'quire side effect failed');
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
/**
|
|
20
|
+
* Tell everyone named in a comment, except whoever wrote it.
|
|
21
|
+
*
|
|
22
|
+
* Notifying somebody that they mentioned themselves is noise, and it is the commonest way an
|
|
23
|
+
* inbox stops being read.
|
|
24
|
+
*/
|
|
25
|
+
async mentions(workspaceId, comment, actorId) {
|
|
26
|
+
const targets = comment.mentionIds.filter((id) => id !== actorId);
|
|
27
|
+
if (targets.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
await best('notifications.create', () => Promise.all(targets.map((userId) => kernel.call('core.notifications.create', {
|
|
30
|
+
workspaceId,
|
|
31
|
+
userId,
|
|
32
|
+
type: 'quire.mention',
|
|
33
|
+
actorId,
|
|
34
|
+
object: { module: 'quire', type: 'page', id: comment.pageId },
|
|
35
|
+
title: comment.bodyText.slice(0, 140),
|
|
36
|
+
body: comment.quotedText.slice(0, 200) || null,
|
|
37
|
+
}))));
|
|
38
|
+
},
|
|
39
|
+
/** Put a page in the workspace search index. */
|
|
40
|
+
async index(documents) {
|
|
41
|
+
if (documents.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
await best('search.index', () => kernel.call('core.search.index', { documents }));
|
|
44
|
+
},
|
|
45
|
+
async unindex(workspaceId, type, ids) {
|
|
46
|
+
if (ids.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
await best('search.remove', () => kernel.call('core.search.remove', {
|
|
49
|
+
refs: ids.map((id) => ({ workspaceId, module: 'quire', type, id })),
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../../src/server/services/notify.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,IAAI,GAAG,KAAK,EAAK,IAAY,EAAE,EAAoB,EAAqB,EAAE;QAC9E,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,0BAA0B,CAAC,CAAA;YACvE,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAA;IAED,OAAO;QACL;;;;;WAKG;QACH,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,OAAqC,EAAE,OAAsB;YAC/F,MAAM,OAAO,GAAI,OAAO,CAAC,UAAuB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAA;YAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAChC,MAAM,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CACtC,OAAO,CAAC,GAAG,CACT,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACrB,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;gBACvC,WAAW;gBACX,MAAM;gBACN,IAAI,EAAE,eAAe;gBACrB,OAAO;gBACP,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE;gBAC7D,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;gBACrC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI;aAC/C,CAAC,CACH,CACF,CACF,CAAA;QACH,CAAC;QAED,gDAAgD;QAChD,KAAK,CAAC,KAAK,CAAC,SAAgC;YAC1C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAClC,MAAM,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;QACnF,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,IAAY,EAAE,GAAa;YAC5D,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAC5B,MAAM,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAC/B,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;gBAChC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;aACpE,CAAC,CACH,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS "mod_quire"."comments" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
3
|
+
"workspace_id" uuid NOT NULL,
|
|
4
|
+
"page_id" uuid NOT NULL,
|
|
5
|
+
"parent_id" uuid,
|
|
6
|
+
"thread_id" uuid NOT NULL,
|
|
7
|
+
"author_id" uuid,
|
|
8
|
+
"body" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
9
|
+
"body_text" text DEFAULT '' NOT NULL,
|
|
10
|
+
"mention_ids" uuid[] DEFAULT '{}'::uuid[] NOT NULL,
|
|
11
|
+
"anchor" jsonb,
|
|
12
|
+
"quoted_text" text DEFAULT '' NOT NULL,
|
|
13
|
+
"resolved_at" timestamp with time zone,
|
|
14
|
+
"resolved_by" uuid,
|
|
15
|
+
"edited_at" timestamp with time zone,
|
|
16
|
+
"deleted_at" timestamp with time zone,
|
|
17
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
18
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
19
|
+
);
|
|
20
|
+
--> statement-breakpoint
|
|
21
|
+
CREATE INDEX IF NOT EXISTS "comments_ws_page_idx" ON "mod_quire"."comments" USING btree ("workspace_id","page_id","created_at");--> statement-breakpoint
|
|
22
|
+
CREATE INDEX IF NOT EXISTS "comments_ws_thread_idx" ON "mod_quire"."comments" USING btree ("workspace_id","thread_id","created_at");--> statement-breakpoint
|
|
23
|
+
|
|
24
|
+
-- Row-level security, the same triple every tenant table gets.
|
|
25
|
+
ALTER TABLE "mod_quire"."comments" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
|
26
|
+
ALTER TABLE "mod_quire"."comments" FORCE ROW LEVEL SECURITY;--> statement-breakpoint
|
|
27
|
+
DROP POLICY IF EXISTS "comments_ws_isolation" ON "mod_quire"."comments";--> statement-breakpoint
|
|
28
|
+
CREATE POLICY "comments_ws_isolation" ON "mod_quire"."comments"
|
|
29
|
+
USING (workspace_id::text = current_setting('app.workspace_id', true))
|
|
30
|
+
WITH CHECK (workspace_id::text = current_setting('app.workspace_id', true));
|