@meith/db 0.34.0 → 0.35.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/package.json +32 -32
- package/src/identity-link-repo.ts +11 -4
- package/src/message-repo.ts +23 -0
- package/src/notification-repo.ts +9 -0
- package/src/post-repo.ts +1 -1
- package/src/post-writes.ts +8 -3
- package/src/subscription-repo.ts +11 -11
- package/src/thread-repo.ts +8 -1
- package/src/user-bulk-repo.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,37 +22,37 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"drizzle-orm": "^0.45.2",
|
|
24
24
|
"postgres": "^3.4.7",
|
|
25
|
-
"@meith/accounts": "0.
|
|
26
|
-
"@meith/admin": "0.
|
|
27
|
-
"@meith/antispam": "0.
|
|
28
|
-
"@meith/api": "0.
|
|
29
|
-
"@meith/attachments": "0.
|
|
30
|
-
"@meith/authorization": "0.
|
|
31
|
-
"@meith/avatars": "0.
|
|
32
|
-
"@meith/backup": "0.
|
|
33
|
-
"@meith/board-digest": "0.
|
|
34
|
-
"@meith/core": "0.
|
|
35
|
-
"@meith/drafts": "0.
|
|
36
|
-
"@meith/events": "0.
|
|
37
|
-
"@meith/
|
|
38
|
-
"@meith/
|
|
39
|
-
"@meith/i18n": "0.
|
|
40
|
-
"@meith/markdown": "0.
|
|
41
|
-
"@meith/marketplace": "0.
|
|
42
|
-
"@meith/messages": "0.
|
|
43
|
-
"@meith/moderation": "0.
|
|
44
|
-
"@meith/notifications": "0.
|
|
45
|
-
"@meith/plugin-kit": "0.
|
|
46
|
-
"@meith/polls": "0.
|
|
47
|
-
"@meith/profile-fields": "0.
|
|
48
|
-
"@meith/relations": "0.
|
|
49
|
-
"@meith/reputation": "0.
|
|
50
|
-
"@meith/search": "0.
|
|
51
|
-
"@meith/settings": "0.
|
|
52
|
-
"@meith/
|
|
53
|
-
"@meith/
|
|
54
|
-
"@meith/tasks": "0.
|
|
55
|
-
"@meith/threads": "0.
|
|
25
|
+
"@meith/accounts": "0.35.0",
|
|
26
|
+
"@meith/admin": "0.35.0",
|
|
27
|
+
"@meith/antispam": "0.35.0",
|
|
28
|
+
"@meith/api": "0.35.0",
|
|
29
|
+
"@meith/attachments": "0.35.0",
|
|
30
|
+
"@meith/authorization": "0.35.0",
|
|
31
|
+
"@meith/avatars": "0.35.0",
|
|
32
|
+
"@meith/backup": "0.35.0",
|
|
33
|
+
"@meith/board-digest": "0.35.0",
|
|
34
|
+
"@meith/core": "0.35.0",
|
|
35
|
+
"@meith/drafts": "0.35.0",
|
|
36
|
+
"@meith/events": "0.35.0",
|
|
37
|
+
"@meith/forums": "0.35.0",
|
|
38
|
+
"@meith/groups": "0.35.0",
|
|
39
|
+
"@meith/i18n": "0.35.0",
|
|
40
|
+
"@meith/markdown": "0.35.0",
|
|
41
|
+
"@meith/marketplace": "0.35.0",
|
|
42
|
+
"@meith/messages": "0.35.0",
|
|
43
|
+
"@meith/moderation": "0.35.0",
|
|
44
|
+
"@meith/notifications": "0.35.0",
|
|
45
|
+
"@meith/plugin-kit": "0.35.0",
|
|
46
|
+
"@meith/polls": "0.35.0",
|
|
47
|
+
"@meith/profile-fields": "0.35.0",
|
|
48
|
+
"@meith/relations": "0.35.0",
|
|
49
|
+
"@meith/reputation": "0.35.0",
|
|
50
|
+
"@meith/search": "0.35.0",
|
|
51
|
+
"@meith/settings": "0.35.0",
|
|
52
|
+
"@meith/signatures": "0.35.0",
|
|
53
|
+
"@meith/subscriptions": "0.35.0",
|
|
54
|
+
"@meith/tasks": "0.35.0",
|
|
55
|
+
"@meith/threads": "0.35.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"drizzle-kit": "^0.31.6",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, asc, eq } from 'drizzle-orm'
|
|
1
|
+
import { and, asc, eq, gt, or, sql } from 'drizzle-orm'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
LinkIdentityInput,
|
|
@@ -136,10 +136,17 @@ export class PostgresPasskeyRepository implements PasskeyRepository {
|
|
|
136
136
|
return removed.length > 0
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
async markUsed(passkeyId: number, signCount: number, now: Date): Promise<
|
|
140
|
-
await this.db
|
|
139
|
+
async markUsed(passkeyId: number, signCount: number, now: Date): Promise<boolean> {
|
|
140
|
+
const rows = await this.db
|
|
141
141
|
.update(passkeys)
|
|
142
142
|
.set({ signCount, lastUsedAt: now })
|
|
143
|
-
.where(
|
|
143
|
+
.where(
|
|
144
|
+
and(
|
|
145
|
+
eq(passkeys.id, passkeyId),
|
|
146
|
+
or(eq(sql`${signCount}`, 0), gt(sql`${signCount}`, passkeys.signCount)),
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
.returning({ id: passkeys.id })
|
|
150
|
+
return rows.length > 0
|
|
144
151
|
}
|
|
145
152
|
}
|
package/src/message-repo.ts
CHANGED
|
@@ -336,6 +336,29 @@ export class PostgresMessageRepository implements MessageRepository {
|
|
|
336
336
|
return rows.length
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
async restore(input: {
|
|
340
|
+
readonly userId: number
|
|
341
|
+
readonly copyIds: readonly number[]
|
|
342
|
+
}): Promise<number> {
|
|
343
|
+
if (input.copyIds.length === 0) return 0
|
|
344
|
+
|
|
345
|
+
const rows = resultRows(
|
|
346
|
+
await this.db.execute(sql`
|
|
347
|
+
update private_message_copies
|
|
348
|
+
set folder = case when role = 'author' then 'sent' else 'inbox' end
|
|
349
|
+
where owner_user_id = ${input.userId}
|
|
350
|
+
and folder = 'trash'
|
|
351
|
+
and id in (${sql.join(
|
|
352
|
+
input.copyIds.map((id) => sql`${id}`),
|
|
353
|
+
sql`, `,
|
|
354
|
+
)})
|
|
355
|
+
returning id
|
|
356
|
+
`),
|
|
357
|
+
) as Array<{ id: number }>
|
|
358
|
+
|
|
359
|
+
return rows.length
|
|
360
|
+
}
|
|
361
|
+
|
|
339
362
|
async remove(input: {
|
|
340
363
|
readonly userId: number
|
|
341
364
|
readonly copyIds: readonly number[]
|
package/src/notification-repo.ts
CHANGED
|
@@ -266,6 +266,15 @@ export class PostgresNotificationRepository implements NotificationRepository {
|
|
|
266
266
|
return rows.length > 0
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
async removeAllPushSubscriptions(userId: number): Promise<number> {
|
|
270
|
+
const rows = resultRows(
|
|
271
|
+
await this.db.execute(sql`
|
|
272
|
+
delete from push_subscriptions where user_id = ${userId} returning id
|
|
273
|
+
`),
|
|
274
|
+
) as Array<{ id: number }>
|
|
275
|
+
return rows.length
|
|
276
|
+
}
|
|
277
|
+
|
|
269
278
|
async pushSubscriptionsFor(userId: number): Promise<readonly PushSubscriptionRecord[]> {
|
|
270
279
|
const rows = resultRows(
|
|
271
280
|
await this.db.execute(sql`
|
package/src/post-repo.ts
CHANGED
|
@@ -111,7 +111,7 @@ export class PostgresPostRepository implements PostRepository {
|
|
|
111
111
|
where r.post_id = ${postId}
|
|
112
112
|
and exists (select 1 from posts p where p.id = r.post_id and p.thread_id = ${threadId})
|
|
113
113
|
union all
|
|
114
|
-
select p.revision_count, p.message, p.subject, p.edited_by_user_id,
|
|
114
|
+
select p.revision_count + 1, p.message, p.subject, p.edited_by_user_id,
|
|
115
115
|
coalesce(u.username, p.author_username, 'Deleted member'),
|
|
116
116
|
p.edit_reason, coalesce(p.edited_at, p.created_at), true
|
|
117
117
|
from posts p
|
package/src/post-writes.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CORE_RENDERING,
|
|
7
7
|
type MarkdownPipeline,
|
|
8
8
|
renderThrough,
|
|
9
|
+
sourceAsMarkdown,
|
|
9
10
|
vocabularyOptions,
|
|
10
11
|
} from '@meith/markdown'
|
|
11
12
|
import type {
|
|
@@ -58,8 +59,9 @@ export class PostgresPostWriteRepository implements PostWriteRepository {
|
|
|
58
59
|
const rows = resultRows(
|
|
59
60
|
await this.db.execute(sql`
|
|
60
61
|
select p.id, p.thread_id, p.forum_id, p.author_user_id, p.subject, p.message,
|
|
61
|
-
p.visibility, p.is_first_post, p.revision_count, p.created_at,
|
|
62
|
-
t.slug as thread_slug, t.title as thread_title,
|
|
62
|
+
p.body_format, p.visibility, p.is_first_post, p.revision_count, p.created_at,
|
|
63
|
+
t.slug as thread_slug, t.title as thread_title,
|
|
64
|
+
t.author_user_id as thread_author_user_id, t.is_locked,
|
|
63
65
|
t.visibility as thread_visibility,
|
|
64
66
|
f.slug as forum_slug, f.is_open
|
|
65
67
|
from posts p
|
|
@@ -74,12 +76,14 @@ export class PostgresPostWriteRepository implements PostWriteRepository {
|
|
|
74
76
|
author_user_id: number | null
|
|
75
77
|
subject: string | null
|
|
76
78
|
message: string
|
|
79
|
+
body_format: number
|
|
77
80
|
visibility: 'visible' | 'unapproved' | 'deleted'
|
|
78
81
|
is_first_post: boolean
|
|
79
82
|
revision_count: number
|
|
80
83
|
created_at: Date
|
|
81
84
|
thread_slug: string
|
|
82
85
|
thread_title: string
|
|
86
|
+
thread_author_user_id: number | null
|
|
83
87
|
is_locked: boolean
|
|
84
88
|
thread_visibility: 'visible' | 'unapproved' | 'deleted'
|
|
85
89
|
forum_slug: string
|
|
@@ -96,7 +100,7 @@ export class PostgresPostWriteRepository implements PostWriteRepository {
|
|
|
96
100
|
forumId: Number(row.forum_id),
|
|
97
101
|
authorUserId: row.author_user_id === null ? null : Number(row.author_user_id),
|
|
98
102
|
subject: row.subject,
|
|
99
|
-
message: row.message,
|
|
103
|
+
message: sourceAsMarkdown(row.message, Number(row.body_format)),
|
|
100
104
|
visibility: row.visibility,
|
|
101
105
|
isFirstPost: row.is_first_post,
|
|
102
106
|
revisionCount: Number(row.revision_count),
|
|
@@ -106,6 +110,7 @@ export class PostgresPostWriteRepository implements PostWriteRepository {
|
|
|
106
110
|
id: Number(row.thread_id),
|
|
107
111
|
slug: row.thread_slug,
|
|
108
112
|
title: row.thread_title,
|
|
113
|
+
authorUserId: row.thread_author_user_id === null ? null : Number(row.thread_author_user_id),
|
|
109
114
|
isLocked: row.is_locked,
|
|
110
115
|
visibility: row.thread_visibility,
|
|
111
116
|
},
|
package/src/subscription-repo.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm'
|
|
2
2
|
|
|
3
|
-
import { PUBLIC_CONTENT } from '@meith/core'
|
|
3
|
+
import { PUBLIC_CONTENT, type ThreadAudience } from '@meith/core'
|
|
4
4
|
import type {
|
|
5
5
|
DigestCadence,
|
|
6
6
|
PendingForUser,
|
|
@@ -15,6 +15,7 @@ import type { Database } from './client'
|
|
|
15
15
|
import { resultRows } from './result-rows'
|
|
16
16
|
import { toDate } from './row-values'
|
|
17
17
|
import { idList } from './sql-lists'
|
|
18
|
+
import { inAudience } from './thread-audience'
|
|
18
19
|
import { visibleIn } from './visibility'
|
|
19
20
|
|
|
20
21
|
interface TableShape {
|
|
@@ -31,11 +32,10 @@ function shapeFor(target: SubscriptionTarget): TableShape {
|
|
|
31
32
|
function pendingPosts(
|
|
32
33
|
userId: number,
|
|
33
34
|
mode: SubscriptionMode | null,
|
|
34
|
-
|
|
35
|
+
audience: ThreadAudience,
|
|
35
36
|
): ReturnType<typeof sql> {
|
|
36
37
|
const byMode = mode === null ? sql`` : sql`and s.mode = ${mode}`
|
|
37
|
-
const visible =
|
|
38
|
-
visibleForumIds === null ? sql`` : sql`and t.forum_id in ${idList(visibleForumIds)}`
|
|
38
|
+
const visible = sql`and ${inAudience(sql`t.forum_id`, sql`t.author_user_id`, audience)}`
|
|
39
39
|
|
|
40
40
|
return sql`
|
|
41
41
|
select p.id as post_id, p.thread_id, t.title as thread_title, t.slug as thread_slug,
|
|
@@ -155,15 +155,15 @@ export class PostgresSubscriptionRepository implements SubscriptionRepository {
|
|
|
155
155
|
|
|
156
156
|
async listFor(
|
|
157
157
|
userId: number,
|
|
158
|
-
options: { readonly
|
|
158
|
+
options: { readonly audience: ThreadAudience; readonly limit: number },
|
|
159
159
|
): Promise<readonly SubscriptionRow[]> {
|
|
160
|
-
const
|
|
160
|
+
const visibleForums = idList(options.audience.forumIds)
|
|
161
161
|
|
|
162
162
|
const rows = resultRows(
|
|
163
163
|
await this.db.execute(sql`
|
|
164
164
|
with pending as (
|
|
165
165
|
select target, target_id, count(*)::int as pending
|
|
166
|
-
from (${pendingPosts(userId, null, options.
|
|
166
|
+
from (${pendingPosts(userId, null, options.audience)}) p
|
|
167
167
|
group by target, target_id
|
|
168
168
|
)
|
|
169
169
|
select 'thread'::text as target, s.thread_id as target_id, t.title,
|
|
@@ -175,7 +175,7 @@ export class PostgresSubscriptionRepository implements SubscriptionRepository {
|
|
|
175
175
|
on pending.target = 'thread' and pending.target_id = s.thread_id
|
|
176
176
|
where s.user_id = ${userId}
|
|
177
177
|
and ${visibleIn(sql`t.visibility`, PUBLIC_CONTENT)}
|
|
178
|
-
and t.forum_id
|
|
178
|
+
and ${inAudience(sql`t.forum_id`, sql`t.author_user_id`, options.audience)}
|
|
179
179
|
|
|
180
180
|
union all
|
|
181
181
|
|
|
@@ -186,7 +186,7 @@ export class PostgresSubscriptionRepository implements SubscriptionRepository {
|
|
|
186
186
|
join forums f on f.id = s.forum_id
|
|
187
187
|
left join pending
|
|
188
188
|
on pending.target = 'forum' and pending.target_id = s.forum_id
|
|
189
|
-
where s.user_id = ${userId} and s.forum_id in ${
|
|
189
|
+
where s.user_id = ${userId} and s.forum_id in ${visibleForums}
|
|
190
190
|
|
|
191
191
|
order by created_at desc, target_id desc
|
|
192
192
|
limit ${options.limit}
|
|
@@ -269,12 +269,12 @@ export class PostgresSubscriptionRepository implements SubscriptionRepository {
|
|
|
269
269
|
async pendingFor(input: {
|
|
270
270
|
readonly userId: number
|
|
271
271
|
readonly mode: SubscriptionMode
|
|
272
|
-
readonly
|
|
272
|
+
readonly audience: ThreadAudience
|
|
273
273
|
readonly limit: number
|
|
274
274
|
}): Promise<PendingForUser> {
|
|
275
275
|
const rows = resultRows(
|
|
276
276
|
await this.db.execute(sql`
|
|
277
|
-
select * from (${pendingPosts(input.userId, input.mode, input.
|
|
277
|
+
select * from (${pendingPosts(input.userId, input.mode, input.audience)}) p
|
|
278
278
|
order by p.post_id
|
|
279
279
|
limit ${input.limit}
|
|
280
280
|
`),
|
package/src/thread-repo.ts
CHANGED
|
@@ -122,12 +122,19 @@ export class PostgresThreadRepository implements ThreadRepository {
|
|
|
122
122
|
.select({
|
|
123
123
|
forumId: threads.forumId,
|
|
124
124
|
authorUserId: threads.authorUserId,
|
|
125
|
+
visibility: threads.visibility,
|
|
125
126
|
})
|
|
126
127
|
.from(threads)
|
|
127
128
|
.where(eq(threads.id, threadId))
|
|
128
129
|
.limit(1)
|
|
129
130
|
const row = rows[0]
|
|
130
|
-
return row
|
|
131
|
+
return row
|
|
132
|
+
? {
|
|
133
|
+
forumId: row.forumId,
|
|
134
|
+
authorUserId: row.authorUserId,
|
|
135
|
+
visibility: row.visibility as ThreadLocation['visibility'],
|
|
136
|
+
}
|
|
137
|
+
: null
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
async findById(
|
package/src/user-bulk-repo.ts
CHANGED
|
@@ -358,7 +358,11 @@ export class PostgresUserBulkRepository {
|
|
|
358
358
|
return sql.join(conditions, sql` and `)
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
async claimMassMailChunk(
|
|
361
|
+
async claimMassMailChunk(
|
|
362
|
+
massMailId: number,
|
|
363
|
+
limit: number,
|
|
364
|
+
enqueue: (recipients: readonly MassMailRecipient[]) => Promise<void>,
|
|
365
|
+
): Promise<MassMailChunk> {
|
|
362
366
|
return this.db.transaction(async (tx) => {
|
|
363
367
|
const mails = resultRows(
|
|
364
368
|
await tx.execute(sql`
|
|
@@ -393,6 +397,8 @@ export class PostgresUserBulkRepository {
|
|
|
393
397
|
const finished = recipients.length < limit
|
|
394
398
|
const cursor = recipients.at(-1)?.userId ?? after
|
|
395
399
|
|
|
400
|
+
await enqueue(recipients)
|
|
401
|
+
|
|
396
402
|
await tx.execute(sql`
|
|
397
403
|
update mass_mails
|
|
398
404
|
set last_user_id = ${cursor},
|