paywall-repo.js
2.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import { randomUUID } from 'node:crypto'
import {
billingFreeQuotaTable,
billingPaywallRuleTable,
billingUnlockTable,
} from '../db/index.js'
const QUOTA = billingFreeQuotaTable.tableName
const RULE = billingPaywallRuleTable.tableName
const UNLOCK = billingUnlockTable.tableName
export function createPaywallRepo(db, runner = db) {
const transactionBound = runner !== db
async function inTransaction(work) {
if (transactionBound) return work(runner)
return db.transaction(work)
}
async function findUnlock({ user_id, content_id, target_key }) {
const row = await runner(UNLOCK).where({ user_id, content_id, target_key }).first()
if (row === undefined) return null
return { ledger_id: row.ledger_id }
}
async function insertUnlock({ user_id, content_id, target_key, ledger_id }) {
await runner(UNLOCK).insert({ id: randomUUID(), user_id, content_id, target_key, ledger_id, created_at: new Date() })
}
async function listUnlocks(user_id, content_id) {
const rows = await runner(UNLOCK).where({ user_id, content_id })
return rows.map(row => ({ target_key: row.target_key, ledger_id: row.ledger_id }))
}
async function getFreeQuotaUsage({ user_id, content_id, kind, window }) {
const row = await runner(QUOTA).where({ user_id, content_id, kind, window_start: window }).first()
if (row === undefined) return 0
return Number(row.used)
}
async function bumpFreeQuota({ user_id, content_id, kind, window }) {
return inTransaction(async trx => {
const existing = await trx(QUOTA).where({ user_id, content_id, kind, window_start: window }).first().forUpdate()
if (existing === undefined) {
await trx(QUOTA).insert({ user_id, content_id, kind, window_start: window, used: 1 })
return
}
await trx(QUOTA).where({ user_id, content_id, kind, window_start: window }).update({ used: Number(existing.used) + 1 })
})
}
async function upsertPaywallRule(rule) {
await runner(RULE).insert({
content_id: rule.content_id,
free_episodes: rule.free_episodes,
free_continue_per_day: rule.free_continue_per_day,
prices_json: JSON.stringify(rule.prices),
active: rule.active,
updated_at: new Date(),
}).onConflict('content_id').merge()
return listPaywallRules(rule.content_id)
}
async function listPaywallRules(content_id) {
const row = await runner(RULE).where({ content_id }).first()
if (row === undefined) return null
return {
free_episodes: Number(row.free_episodes),
free_continue_per_day: Number(row.free_continue_per_day),
prices: JSON.parse(row.prices_json),
}
}
return { findUnlock, insertUnlock, listUnlocks, getFreeQuotaUsage, bumpFreeQuota, upsertPaywallRule, listPaywallRules }
}