mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-28 15:51:29 +08:00
2c8e2cc837
* feat: add in-app notification system (站内信) — schema, service, API, Bell UI - Add `notifications` table to DB schema with userId/type/title/body/refType/refId/metadata/readAt/createdAt fields; two indexes for list & unread queries - Migration `0010_notifications.sql` created manually (drizzle-kit requires TTY) - Service layer: createNotification, listNotifications (paginated + unreadOnly filter), markAsRead (idempotent, owner-only), markAllAsRead, unreadCount - REST API at `/api/notifications`: list + unreadCount, GET unread-count, POST :id/read (204), POST read-all - Shared `Notification` type, `listNotificationsQuerySchema`, RPC client export - NotificationBell (badge, 30s polling), NotificationDropdown, NotificationItem components injected into AppSidebar footer - Bell badge capped at "9+"; unread items bold; click marks read + navigates via refType/refId - i18n: en + zh translations for all notification keys - 26 Node integration tests + 5 CF smoke tests; all 1884 + 26 tests pass Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f * test: add notification API wrapper tests and component logic tests; fix dead condition - Add tests for listNotifications, getUnreadCount, markNotificationRead, markAllNotificationsRead in api.test.ts - Add notification-bell.test.ts: badge label logic (0/5/"9+" cap) and polling interval - Add notification-dropdown.test.ts: mark-all-read visibility, empty state, query key - Add notification-item.test.ts: resolveHref (share token nav, malformed JSON), diffMinutes, isUnread, title style - Fix dead condition in markNotificationRead: simplify `!res.ok && res.status !== 204` → `!res.ok` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: trigger CI check run for test coverage fixes --------- Co-authored-by: Bob <aibob@mails.agent-kanban.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
import { zValidator } from '@hono/zod-validator'
|
|
import { Hono } from 'hono'
|
|
import { listNotificationsQuerySchema } from '../../shared/schemas'
|
|
import { requireAuth } from '../middleware/auth'
|
|
import type { Env } from '../middleware/platform'
|
|
import { listNotifications, markAllAsRead, markAsRead, unreadCount } from '../services/notification'
|
|
|
|
export const notifications = new Hono<Env>()
|
|
.use(requireAuth)
|
|
.get('/', zValidator('query', listNotificationsQuerySchema), async (c) => {
|
|
const db = c.get('platform').db
|
|
const userId = c.get('userId')!
|
|
const { page: pageStr, pageSize: pageSizeStr, unread } = c.req.valid('query')
|
|
const page = Number(pageStr ?? '1')
|
|
const pageSize = Number(pageSizeStr ?? '20')
|
|
const unreadOnly = unread === 'true'
|
|
|
|
const result = await listNotifications(db, userId, { page, pageSize, unreadOnly })
|
|
return c.json({ ...result, page, pageSize })
|
|
})
|
|
.get('/unread-count', async (c) => {
|
|
const db = c.get('platform').db
|
|
const userId = c.get('userId')!
|
|
const count = await unreadCount(db, userId)
|
|
return c.json({ count })
|
|
})
|
|
.post('/:id/read', async (c) => {
|
|
const db = c.get('platform').db
|
|
const userId = c.get('userId')!
|
|
const { id } = c.req.param()
|
|
|
|
const found = await markAsRead(db, userId, id)
|
|
if (!found) return c.json({ error: 'Not found' }, 404)
|
|
|
|
return new Response(null, { status: 204 })
|
|
})
|
|
.post('/read-all', async (c) => {
|
|
const db = c.get('platform').db
|
|
const userId = c.get('userId')!
|
|
const result = await markAllAsRead(db, userId)
|
|
return c.json(result)
|
|
})
|