feat: add health, miniapp stub, and sync routes
This commit is contained in:
parent
4a8b0ac806
commit
c16d028983
3 changed files with 100 additions and 0 deletions
4
src/routes/health.ts
Normal file
4
src/routes/health.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
export const health = new Hono()
|
||||||
|
health.get('/health', c => c.json({ ok: true }))
|
||||||
12
src/routes/miniapp.ts
Normal file
12
src/routes/miniapp.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const HTML = `<!doctype html>
|
||||||
|
<html><head><meta charset="utf-8"><title>arcanesync</title>
|
||||||
|
<script src="https://telegram.org/js/telegram-web-app.js"></script></head>
|
||||||
|
<body style="font:14px system-ui;padding:24px;color:#888">
|
||||||
|
<p>managed by arcanegram client. you can close this window.</p>
|
||||||
|
<script>try { Telegram.WebApp.close() } catch {}</script>
|
||||||
|
</body></html>`
|
||||||
|
|
||||||
|
export const miniapp = new Hono()
|
||||||
|
miniapp.get('/miniapp', c => c.html(HTML))
|
||||||
84
src/routes/sync.ts
Normal file
84
src/routes/sync.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import type { AuthEnv } from '@/middleware/auth.js'
|
||||||
|
import { Buffer } from 'node:buffer'
|
||||||
|
import { eq, sql } from 'drizzle-orm'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { userSettings } from '@/db/schema.js'
|
||||||
|
import { db } from '@/lib/db.js'
|
||||||
|
import { authMiddleware } from '@/middleware/auth.js'
|
||||||
|
import { config } from '@/shared/config.js'
|
||||||
|
|
||||||
|
export const sync = new Hono<AuthEnv>()
|
||||||
|
sync.use('*', authMiddleware)
|
||||||
|
|
||||||
|
sync.get('/sync', async (c) => {
|
||||||
|
const userId = c.get('userId')
|
||||||
|
const [row] = await db.select().from(userSettings).where(eq(userSettings.userId, userId))
|
||||||
|
if (!row)
|
||||||
|
return c.json({ version: 0, data: {} })
|
||||||
|
return c.json({ version: row.version, data: row.data })
|
||||||
|
})
|
||||||
|
|
||||||
|
interface PutBody {
|
||||||
|
baseVersion: number
|
||||||
|
patch: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
sync.put('/sync', async (c) => {
|
||||||
|
const raw = await c.req.text()
|
||||||
|
if (Buffer.byteLength(raw, 'utf8') > config.maxPayloadBytes)
|
||||||
|
return c.json({ error: 'payload-too-large' }, 413)
|
||||||
|
|
||||||
|
let body: PutBody
|
||||||
|
try {
|
||||||
|
body = JSON.parse(raw)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return c.json({ error: 'bad-json' }, 400)
|
||||||
|
}
|
||||||
|
if (typeof body.baseVersion !== 'number' || typeof body.patch !== 'object' || body.patch === null || Array.isArray(body.patch))
|
||||||
|
return c.json({ error: 'malformed' }, 400)
|
||||||
|
|
||||||
|
const userId = c.get('userId')
|
||||||
|
|
||||||
|
return db.transaction(async (tx) => {
|
||||||
|
const [row] = await tx
|
||||||
|
.select()
|
||||||
|
.from(userSettings)
|
||||||
|
.where(eq(userSettings.userId, userId))
|
||||||
|
.for('update')
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
const merged = applyPatch({}, body.patch)
|
||||||
|
const [inserted] = await tx
|
||||||
|
.insert(userSettings)
|
||||||
|
.values({ userId, version: 1, data: merged })
|
||||||
|
.returning()
|
||||||
|
return c.json({ version: inserted.version, data: inserted.data })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.baseVersion !== row.version)
|
||||||
|
return c.json({ version: row.version, data: row.data }, 409)
|
||||||
|
|
||||||
|
const merged = applyPatch(row.data, body.patch)
|
||||||
|
const [updated] = await tx
|
||||||
|
.update(userSettings)
|
||||||
|
.set({ version: row.version + 1, data: merged, updatedAt: sql`now()` })
|
||||||
|
.where(eq(userSettings.userId, userId))
|
||||||
|
.returning()
|
||||||
|
return c.json({ version: updated.version, data: updated.data })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function applyPatch(
|
||||||
|
base: Record<string, unknown>,
|
||||||
|
patch: Record<string, unknown>,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const out: Record<string, unknown> = { ...base }
|
||||||
|
for (const [k, v] of Object.entries(patch)) {
|
||||||
|
if (v === null)
|
||||||
|
delete out[k]
|
||||||
|
else
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue