Recurring event editing (rrule) with this/all occurrence scope, web push subscriptions (VAPID) with a cached config layer for notification settings, and email delivery via nodemailer. Also fixes the calendar view never actually stacking on mobile: the Calendars filter column used flex:1 with minWidth:0 on its sibling, so flex-wrap never triggered regardless of viewport width, squeezing the grid and view switcher into a sliver next to a fixed 220px sidebar. Adds a proper mobile breakpoint that stacks the layout, scrolls the week grid horizontally instead of compressing it, and enlarges touch targets.
28 lines
1.2 KiB
JavaScript
28 lines
1.2 KiB
JavaScript
import { requireAuth, requireCap } from '../auth.js'
|
|
import { pool } from '../db.js'
|
|
|
|
export async function configRoutes(app) {
|
|
app.addHook('preHandler', requireAuth)
|
|
|
|
// GET /api/config — all config keys as a flat object
|
|
app.get('/api/config', { preHandler: requireCap('view') }, async () => {
|
|
const { rows } = await pool.query('SELECT key, value FROM config ORDER BY key')
|
|
return Object.fromEntries(rows.map(r => [r.key, r.value]))
|
|
})
|
|
|
|
// PATCH /api/config/:key — update a single config key. Gated on 'admin'
|
|
// (same cap that already gates the activity log) rather than a new
|
|
// capability — nothing about notification settings warrants a separate one.
|
|
app.patch('/api/config/:key', { preHandler: requireCap('admin') }, async (req, reply) => {
|
|
const { key } = req.params
|
|
const { value } = req.body || {}
|
|
if (value === undefined) return reply.status(400).send({ error: 'value required' })
|
|
|
|
await pool.query(
|
|
`INSERT INTO config (key, value, updated_at) VALUES ($1, $2, NOW())
|
|
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()`,
|
|
[key, JSON.stringify(value)]
|
|
)
|
|
return { ok: true }
|
|
})
|
|
}
|