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