@meith/db 0.17.1 → 0.18.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/LICENSE.md +21 -165
- package/migrations/0057_promotion_scan_cursor.sql +6 -0
- package/migrations/meta/0057_snapshot.json +9487 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +31 -31
- package/src/index.ts +1 -0
- package/src/install-repo.ts +18 -7
- package/src/marketplace-repo.ts +12 -19
- package/src/promotion-repo.ts +25 -1
- package/src/schema/platform.ts +7 -0
|
@@ -400,6 +400,13 @@
|
|
|
400
400
|
"when": 1787524084640,
|
|
401
401
|
"tag": "0056_marketplace_catalog",
|
|
402
402
|
"breakpoints": true
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"idx": 57,
|
|
406
|
+
"version": "7",
|
|
407
|
+
"when": 1787650466674,
|
|
408
|
+
"tag": "0057_promotion_scan_cursor",
|
|
409
|
+
"breakpoints": true
|
|
403
410
|
}
|
|
404
411
|
]
|
|
405
412
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/db",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "0.18.0",
|
|
4
|
+
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/meith-dev/meith.git",
|
|
@@ -22,35 +22,35 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"drizzle-orm": "^0.45.2",
|
|
24
24
|
"postgres": "^3.4.7",
|
|
25
|
-
"@meith/
|
|
26
|
-
"@meith/
|
|
27
|
-
"@meith/
|
|
28
|
-
"@meith/
|
|
29
|
-
"@meith/
|
|
30
|
-
"@meith/
|
|
31
|
-
"@meith/
|
|
32
|
-
"@meith/
|
|
33
|
-
"@meith/
|
|
34
|
-
"@meith/groups": "0.
|
|
35
|
-
"@meith/
|
|
36
|
-
"@meith/events": "0.
|
|
37
|
-
"@meith/i18n": "0.
|
|
38
|
-
"@meith/
|
|
39
|
-
"@meith/
|
|
40
|
-
"@meith/moderation": "0.
|
|
41
|
-
"@meith/plugin-kit": "0.
|
|
42
|
-
"@meith/
|
|
43
|
-
"@meith/
|
|
44
|
-
"@meith/
|
|
45
|
-
"@meith/
|
|
46
|
-
"@meith/
|
|
47
|
-
"@meith/
|
|
48
|
-
"@meith/
|
|
49
|
-
"@meith/
|
|
50
|
-
"@meith/
|
|
51
|
-
"@meith/
|
|
52
|
-
"@meith/
|
|
53
|
-
"@meith/
|
|
25
|
+
"@meith/admin": "0.18.0",
|
|
26
|
+
"@meith/accounts": "0.18.0",
|
|
27
|
+
"@meith/api": "0.18.0",
|
|
28
|
+
"@meith/antispam": "0.18.0",
|
|
29
|
+
"@meith/attachments": "0.18.0",
|
|
30
|
+
"@meith/authorization": "0.18.0",
|
|
31
|
+
"@meith/avatars": "0.18.0",
|
|
32
|
+
"@meith/core": "0.18.0",
|
|
33
|
+
"@meith/forums": "0.18.0",
|
|
34
|
+
"@meith/groups": "0.18.0",
|
|
35
|
+
"@meith/drafts": "0.18.0",
|
|
36
|
+
"@meith/events": "0.18.0",
|
|
37
|
+
"@meith/i18n": "0.18.0",
|
|
38
|
+
"@meith/markdown": "0.18.0",
|
|
39
|
+
"@meith/marketplace": "0.18.0",
|
|
40
|
+
"@meith/moderation": "0.18.0",
|
|
41
|
+
"@meith/plugin-kit": "0.18.0",
|
|
42
|
+
"@meith/polls": "0.18.0",
|
|
43
|
+
"@meith/messages": "0.18.0",
|
|
44
|
+
"@meith/reputation": "0.18.0",
|
|
45
|
+
"@meith/notifications": "0.18.0",
|
|
46
|
+
"@meith/profile-fields": "0.18.0",
|
|
47
|
+
"@meith/search": "0.18.0",
|
|
48
|
+
"@meith/relations": "0.18.0",
|
|
49
|
+
"@meith/subscriptions": "0.18.0",
|
|
50
|
+
"@meith/settings": "0.18.0",
|
|
51
|
+
"@meith/tasks": "0.18.0",
|
|
52
|
+
"@meith/threads": "0.18.0",
|
|
53
|
+
"@meith/signatures": "0.18.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"drizzle-kit": "^0.31.6",
|
package/src/index.ts
CHANGED
package/src/install-repo.ts
CHANGED
|
@@ -50,17 +50,28 @@ export async function markInstalled(db: Database, version: string): Promise<bool
|
|
|
50
50
|
return rows.length > 0
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export async function
|
|
54
|
-
|
|
53
|
+
export async function tryInstallLock<T>(
|
|
54
|
+
sql: ReturnType<typeof postgres>,
|
|
55
|
+
body: () => Promise<T>,
|
|
56
|
+
): Promise<T | null> {
|
|
57
|
+
const rows = await sql`
|
|
58
|
+
select pg_try_advisory_lock(${INSTALL_LOCK_KEY}::bigint) as locked
|
|
59
|
+
`
|
|
60
|
+
|
|
61
|
+
if (rows[0]?.locked !== true) return null
|
|
55
62
|
|
|
56
63
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`
|
|
64
|
+
return await body()
|
|
65
|
+
} finally {
|
|
66
|
+
await sql`select pg_advisory_unlock(${INSTALL_LOCK_KEY}::bigint)`.catch(() => undefined)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
export async function withInstallLock<T>(url: string, body: () => Promise<T>): Promise<T | null> {
|
|
71
|
+
const client = postgres(url, { max: 1, prepare: false, onnotice: () => {} })
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
try {
|
|
74
|
+
return await tryInstallLock(client, body)
|
|
64
75
|
} finally {
|
|
65
76
|
await client.end({ timeout: 5 })
|
|
66
77
|
}
|
package/src/marketplace-repo.ts
CHANGED
|
@@ -82,30 +82,23 @@ export class PostgresMarketplaceCacheRepository implements MarketplaceCacheRepos
|
|
|
82
82
|
`)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
async
|
|
85
|
+
async claimNotified(key: string, version: string): Promise<boolean> {
|
|
86
|
+
const marker = `${key}@${version}`
|
|
86
87
|
const rows = resultRows(
|
|
87
88
|
await this.db.execute(sql`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
insert into marketplace_catalog (id, notified_updates, updated_at)
|
|
90
|
+
values (1, ${JSON.stringify([marker])}::jsonb, now())
|
|
91
|
+
on conflict (id) do update
|
|
92
|
+
set notified_updates = (
|
|
93
|
+
select jsonb_agg(distinct value)
|
|
94
|
+
from jsonb_array_elements(marketplace_catalog.notified_updates || excluded.notified_updates)
|
|
95
|
+
),
|
|
96
|
+
updated_at = now()
|
|
97
|
+
where not (marketplace_catalog.notified_updates @> excluded.notified_updates)
|
|
98
|
+
returning 1
|
|
92
99
|
`),
|
|
93
100
|
) as unknown[]
|
|
94
101
|
|
|
95
102
|
return rows.length > 0
|
|
96
103
|
}
|
|
97
|
-
|
|
98
|
-
async markNotified(key: string, version: string): Promise<void> {
|
|
99
|
-
const marker = `${key}@${version}`
|
|
100
|
-
await this.db.execute(sql`
|
|
101
|
-
insert into marketplace_catalog (id, notified_updates, updated_at)
|
|
102
|
-
values (1, ${JSON.stringify([marker])}::jsonb, now())
|
|
103
|
-
on conflict (id) do update
|
|
104
|
-
set notified_updates = (
|
|
105
|
-
select jsonb_agg(distinct value)
|
|
106
|
-
from jsonb_array_elements(marketplace_catalog.notified_updates || excluded.notified_updates)
|
|
107
|
-
),
|
|
108
|
-
updated_at = now()
|
|
109
|
-
`)
|
|
110
|
-
}
|
|
111
104
|
}
|
package/src/promotion-repo.ts
CHANGED
|
@@ -13,9 +13,11 @@ import { promotionRuleProblem } from '@meith/groups'
|
|
|
13
13
|
import { msg } from '@meith/i18n'
|
|
14
14
|
|
|
15
15
|
import type { Database } from './client'
|
|
16
|
-
import { groupPromotions, users } from './schema'
|
|
16
|
+
import { groupPromotions, promotionScanState, users } from './schema'
|
|
17
17
|
import { keptDisplayGroupSql } from './staff-groups'
|
|
18
18
|
|
|
19
|
+
const SCAN_STATE_ID = 'default'
|
|
20
|
+
|
|
19
21
|
function optionalNumber(value: number | null): number | undefined {
|
|
20
22
|
return value === null ? undefined : value
|
|
21
23
|
}
|
|
@@ -120,6 +122,28 @@ export class PostgresPromotionRepository implements PromotionRepository, Promoti
|
|
|
120
122
|
return rows
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
async scanCursor(): Promise<number> {
|
|
126
|
+
const rows = await this.db
|
|
127
|
+
.select({ cursor: promotionScanState.cursor })
|
|
128
|
+
.from(promotionScanState)
|
|
129
|
+
.where(eq(promotionScanState.id, SCAN_STATE_ID))
|
|
130
|
+
|
|
131
|
+
return Number(rows[0]?.cursor ?? 0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async advanceScanCursor(afterUserId: number): Promise<void> {
|
|
135
|
+
const completedPass = afterUserId === 0
|
|
136
|
+
|
|
137
|
+
await this.db.execute(sql`
|
|
138
|
+
insert into promotion_scan_state (id, cursor, passes, updated_at)
|
|
139
|
+
values (${SCAN_STATE_ID}, ${afterUserId}, ${completedPass ? 1 : 0}, now())
|
|
140
|
+
on conflict (id) do update
|
|
141
|
+
set cursor = ${afterUserId},
|
|
142
|
+
passes = promotion_scan_state.passes + ${completedPass ? 1 : 0},
|
|
143
|
+
updated_at = now()
|
|
144
|
+
`)
|
|
145
|
+
}
|
|
146
|
+
|
|
123
147
|
async applyPromotions(outcomes: readonly PromotionOutcome[]): Promise<void> {
|
|
124
148
|
if (outcomes.length === 0) return
|
|
125
149
|
|
package/src/schema/platform.ts
CHANGED
|
@@ -248,6 +248,13 @@ export const counterRecountState = pgTable('counter_recount_state', {
|
|
|
248
248
|
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
249
249
|
})
|
|
250
250
|
|
|
251
|
+
export const promotionScanState = pgTable('promotion_scan_state', {
|
|
252
|
+
id: text('id').primaryKey(),
|
|
253
|
+
cursor: integer('cursor').notNull().default(0),
|
|
254
|
+
passes: integer('passes').notNull().default(0),
|
|
255
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
256
|
+
})
|
|
257
|
+
|
|
251
258
|
export const massMails = pgTable('mass_mails', {
|
|
252
259
|
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
|
|
253
260
|
subject: text('subject').notNull(),
|