@meith/db 0.33.4 → 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/migrations/0068_backup_runs.sql +22 -0
- package/migrations/0069_backup_runs_active.sql +1 -0
- package/migrations/meta/0068_snapshot.json +9864 -0
- package/migrations/meta/0069_snapshot.json +9880 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +32 -31
- package/src/backup-run-repo.ts +212 -0
- package/src/identity-link-repo.ts +11 -4
- package/src/index.ts +7 -1
- 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/schema/platform.ts +39 -0
- package/src/settings-repo.ts +64 -4
- package/src/subscription-repo.ts +11 -11
- package/src/system-health-repo.ts +2 -1
- package/src/task-repo.ts +14 -0
- package/src/thread-repo.ts +8 -1
- package/src/user-bulk-repo.ts +7 -1
- package/src/user-merge-map.ts +1 -0
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
|
`),
|
|
@@ -41,7 +41,7 @@ export class PostgresSystemHealthRepository {
|
|
|
41
41
|
const rows = resultRows(
|
|
42
42
|
await this.db.execute(sql`
|
|
43
43
|
select key, interval_seconds, enabled, last_run_at, next_run_at,
|
|
44
|
-
consecutive_failures
|
|
44
|
+
locked_until, consecutive_failures
|
|
45
45
|
from tasks order by key
|
|
46
46
|
`),
|
|
47
47
|
) as Array<Record<string, unknown>>
|
|
@@ -52,6 +52,7 @@ export class PostgresSystemHealthRepository {
|
|
|
52
52
|
enabled: row.enabled === true,
|
|
53
53
|
lastRunAt: row.last_run_at === null ? null : toDate(row.last_run_at),
|
|
54
54
|
nextRunAt: row.next_run_at === null ? null : toDate(row.next_run_at),
|
|
55
|
+
lockedUntil: row.locked_until === null ? null : toDate(row.locked_until),
|
|
55
56
|
consecutiveFailures: Number(row.consecutive_failures),
|
|
56
57
|
}))
|
|
57
58
|
}
|
package/src/task-repo.ts
CHANGED
|
@@ -49,6 +49,20 @@ export class PostgresTaskRepository implements TaskRepository {
|
|
|
49
49
|
})
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
async renew(input: { taskId: string; now: Date; staleBefore: Date }): Promise<boolean> {
|
|
53
|
+
const leaseMs = input.now.getTime() - input.staleBefore.getTime()
|
|
54
|
+
const lockedUntil = new Date(input.now.getTime() + leaseMs)
|
|
55
|
+
const result = await this.db.execute(sql`
|
|
56
|
+
update tasks
|
|
57
|
+
set locked_until = ${lockedUntil}
|
|
58
|
+
where key = ${input.taskId}
|
|
59
|
+
and locked_until is not null
|
|
60
|
+
and locked_until > ${input.now}
|
|
61
|
+
returning key
|
|
62
|
+
`)
|
|
63
|
+
return resultRows(result).length === 1
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
async claim(input: {
|
|
53
67
|
taskId: string
|
|
54
68
|
now: Date
|
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},
|
package/src/user-merge-map.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type DiscardColumn = ReassignColumn
|
|
|
15
15
|
export const MERGE_REASSIGN: readonly ReassignColumn[] = [
|
|
16
16
|
{ table: 'admin_log', column: 'user_id' },
|
|
17
17
|
{ table: 'admin_undo_operations', column: 'actor_user_id' },
|
|
18
|
+
{ table: 'backup_runs', column: 'requested_by_user_id' },
|
|
18
19
|
{ table: 'attachments', column: 'uploader_user_id' },
|
|
19
20
|
{ table: 'ban_filters', column: 'created_by_user_id' },
|
|
20
21
|
{ table: 'bans', column: 'banned_by_user_id' },
|