product-repo.js
2.65 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
import { billingProductTable } from '../db/index.js'
const PRODUCT = billingProductTable.tableName
export function createProductRepo(db, runner = db) {
async function listProducts() {
const rows = await runner(PRODUCT).where({ active: true }).orderBy('price_cents')
return rows.map(productFromRow)
}
async function listAllProducts() {
const rows = await runner(PRODUCT).orderBy('price_cents')
return rows.map(productFromRow)
}
async function findProduct(id) {
const row = await runner(PRODUCT).where({ id }).first()
if (row === undefined) return null
return productFromRow(row)
}
async function saveProduct(product) {
await runner(PRODUCT).insert(productToRow(product)).onConflict('id').merge()
return findProduct(product.id)
}
return { listProducts, listAllProducts, findProduct, saveProduct }
}
function productFromRow(row) {
const id = requiredString(row.id, 'billing_product.id')
const kind = requiredString(row.kind, 'billing_product.kind', id)
const product = {
id,
kind,
title: requiredString(row.title, 'billing_product.title', id),
points: requiredInteger(row.points, 'billing_product.points', id),
paid_points: requiredInteger(row.paid_points, 'billing_product.paid_points', id),
bonus_points: requiredInteger(row.bonus_points, 'billing_product.bonus_points', id),
price_cents: requiredInteger(row.price_cents, 'billing_product.price_cents', id),
price_usd: Number(row.price_usd),
currency: requiredString(row.currency, 'billing_product.currency', id),
stripe_price_id: nullableString(row.stripe_price_id, 'billing_product.stripe_price_id', id),
active: Boolean(row.active),
}
if (kind !== 'points_pack' || product.points !== product.paid_points + product.bonus_points) {
throw new Error(`Invalid billing_product points_pack, id=${id}`)
}
return product
}
function productToRow(product) {
if (product.kind !== 'points_pack') throw new Error(`Unsupported billing_product.kind: ${product.kind}, id: ${product.id}`)
if (product.points !== product.paid_points + product.bonus_points) throw new Error(`Invalid billing_product points total, id=${product.id}`)
return { ...product, updated_at: new Date() }
}
function requiredString(value, field, id = 'unknown') {
if (typeof value !== 'string' || value === '') throw new Error(`Invalid ${field}, id=${id}`)
return value
}
function nullableString(value, field, id) {
if (value === null) return null
return requiredString(value, field, id)
}
function requiredInteger(value, field, id) {
const number = Number(value)
if (!Number.isSafeInteger(number)) throw new Error(`Invalid ${field}, id=${id}`)
return number
}