Add recurring events, push notifications, config sync — and mobile layout fixes

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.
This commit is contained in:
jtricerolph 2026-07-25 23:04:59 +00:00
parent dad3cc472c
commit ca0dc9b070
28 changed files with 1517 additions and 155 deletions

View file

@ -0,0 +1,28 @@
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 }
})
}