feat(mim): admin QR/brand API, public config hooks, and deploy workflow fix.

Split mim-api into modular routes, refresh nav brand assets, and pin reusable validate workflow to Order-of-Hospitallers/miracles_in_motion@main.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
defiQUG
2026-06-19 16:17:10 -07:00
co-authored by Cursor
parent 4e4df13ab1
commit 05978ae25c
66 changed files with 8233 additions and 3903 deletions
+37
View File
@@ -0,0 +1,37 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { defaultMimCustomizations } from '../src/lib/qrcg.js'
import { defaultMonkeyConfig } from '../src/lib/qr-monkey.js'
import { mimQrBrandSpec } from '../src/lib/qr-brand.js'
import { getQrProvider } from '../src/lib/qr-provider.js'
describe('qr brand customizations', () => {
it('qrcg defaults use forest green / gold diamond styling', () => {
const c = defaultMimCustomizations()
assert.equal(c.background.color, '#1a3c34')
assert.equal(c.pattern.color, '#c5a059')
assert.equal(c.pattern.shape, 'diamond')
})
it('qrcode monkey defaults match approved reference', () => {
const c = defaultMonkeyConfig('https://mim4u.org')
assert.equal(c.bgColor, '#1a3c34')
assert.equal(c.bodyColor, '#c5a059')
assert.equal(c.body, 'diamond')
assert.equal(c.eye, 'frame14')
assert.match(c.logo, /logo-square/)
})
it('exposes brand spec with reference image path', () => {
const spec = mimQrBrandSpec('https://mim4u.org')
assert.equal(spec.referenceImage, '/brand/qr-code-branded-reference.png')
assert.equal(spec.colors.background, '#1a3c34')
})
it('defaults to qrcode-monkey provider', () => {
const prev = process.env.MIM_QR_PROVIDER
delete process.env.MIM_QR_PROVIDER
assert.equal(getQrProvider(), 'qrcode-monkey')
if (prev) process.env.MIM_QR_PROVIDER = prev
})
})
+114 -16
View File
@@ -1,21 +1,9 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
/** Inline validation mirror — keep in sync with server.js validateAssistance */
function validateAssistance(body) {
const errors = []
if (!body?.requestType) errors.push('requestType required')
if (!body?.studentInfo?.firstName?.trim()) errors.push('studentInfo.firstName required')
if (!body?.studentInfo?.lastName?.trim()) errors.push('studentInfo.lastName required')
if (!body?.studentInfo?.school?.trim()) errors.push('studentInfo.school required')
if (!body?.contactInfo?.parentName?.trim()) errors.push('contactInfo.parentName required')
if (!body?.contactInfo?.phone?.trim()) errors.push('contactInfo.phone required')
if (!body?.contactInfo?.email?.trim()) errors.push('contactInfo.email required')
if (!body?.contactInfo?.relationship?.trim()) errors.push('contactInfo.relationship required')
if (!body?.details?.trim()) errors.push('details required')
if (body?.website) errors.push('spam detected')
return errors
}
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { createApp, validateAssistance } from '../src/app.js'
describe('assistance validation shape', () => {
it('requires core fields', () => {
@@ -28,3 +16,113 @@ describe('assistance validation shape', () => {
assert.ok(errors.includes('spam detected'))
})
})
describe('admin auth and settings', () => {
it('logs in admin and updates stripe settings', async () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mim-api-test-'))
const { app, AUTH_SECRET } = createApp({ dataDir: tmp, authSecret: 'test-secret', port: 0 })
const server = app.listen(0)
const { port } = server.address()
const loginRes = await fetch(`http://127.0.0.1:${port}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: '[email protected]', password: 'demo123' }),
})
assert.equal(loginRes.status, 200)
const { token } = await loginRes.json()
assert.ok(token)
const settingsRes = await fetch(`http://127.0.0.1:${port}/api/admin/settings`, {
headers: { Authorization: `Bearer ${token}` },
})
assert.equal(settingsRes.status, 200)
const patchRes = await fetch(`http://127.0.0.1:${port}/api/admin/settings`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
donationsEnabled: true,
stripePublishableKey: 'pk_test_abc',
stripeSecretKey: 'sk_test_abc',
stripeWebhookSecret: 'whsec_test',
}),
})
assert.equal(patchRes.status, 200)
const patched = await patchRes.json()
assert.equal(patched.donationsEnabled, true)
assert.match(patched.stripeSecretKey, /…/)
const publicRes = await fetch(`http://127.0.0.1:${port}/api/public/config`)
const pub = await publicRes.json()
assert.equal(pub.stripeConfigured, true)
assert.equal(pub.stripePublishableKey, 'pk_test_abc')
server.close()
fs.rmSync(tmp, { recursive: true, force: true })
assert.ok(AUTH_SECRET)
})
})
describe('qr admin routes', () => {
it('reports qrcode-monkey as default provider', async () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mim-api-qr-'))
const { app } = createApp({ dataDir: tmp, authSecret: 'test-secret', port: 0 })
const server = app.listen(0)
const { port } = server.address()
const loginRes = await fetch(`http://127.0.0.1:${port}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: '[email protected]', password: 'demo123' }),
})
const { token } = await loginRes.json()
const statusRes = await fetch(`http://127.0.0.1:${port}/api/admin/qrcodes/status`, {
headers: { Authorization: `Bearer ${token}` },
})
assert.equal(statusRes.status, 200)
const status = await statusRes.json()
assert.equal(status.provider, 'qrcode-monkey')
assert.equal(status.configured, true)
assert.equal(status.dynamicTracking, false)
server.close()
fs.rmSync(tmp, { recursive: true, force: true })
})
})
describe('brand manifest', () => {
it('seeds and serves public brand manifest', async () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mim-api-brand-'))
const { app } = createApp({ dataDir: tmp, authSecret: 'test-secret', port: 0 })
const server = app.listen(0)
const { port } = server.address()
const publicRes = await fetch(`http://127.0.0.1:${port}/api/public/brand`)
assert.equal(publicRes.status, 200)
const manifest = await publicRes.json()
assert.ok(manifest.groups?.length > 0)
assert.ok(manifest.colors?.length > 0)
const loginRes = await fetch(`http://127.0.0.1:${port}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: '[email protected]', password: 'demo123' }),
})
const { token } = await loginRes.json()
const patchRes = await fetch(`http://127.0.0.1:${port}/api/admin/brand`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ published: false }),
})
assert.equal(patchRes.status, 200)
const hidden = await fetch(`http://127.0.0.1:${port}/api/public/brand`)
const hiddenJson = await hidden.json()
assert.equal(hiddenJson.published, false)
server.close()
fs.rmSync(tmp, { recursive: true, force: true })
})
})