Files
zpan/shared/types/index.ts
T
Jasper Van 2c8e2cc837 feat: v2.3.0 T1 — 站内信系统 (in-app notifications) (#307)
* 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>
2026-04-20 01:23:54 -04:00

150 lines
2.6 KiB
TypeScript

import type { DirType, ObjectStatus, StorageMode, StorageStatus } from '../constants'
export interface StorageObject {
id: string
orgId: string
alias: string
name: string
type: string
size: number
dirtype: DirType
parent: string
object: string
storageId: string
status: ObjectStatus
createdAt: string
updatedAt: string
}
export interface Storage {
id: string
uid: string
title: string
mode: StorageMode
bucket: string
endpoint: string
region: string
accessKey: string
secretKey: string
customHost: string
capacity: number
used: number
status: StorageStatus
createdAt: string
updatedAt: string
}
export interface OrgQuota {
id: string
orgId: string
quota: number
used: number
}
export interface Organization {
id: string
name: string
slug: string
logo: string | null
metadata: string | null
createdAt: string
updatedAt: string | null
}
export interface Member {
id: string
organizationId: string
userId: string
role: string
createdAt: string
}
export interface Invitation {
id: string
organizationId: string
email: string
role: string
status: 'pending' | 'accepted' | 'rejected' | 'canceled'
expiresAt: string
inviterId: string
createdAt: string
}
export interface SystemOption {
key: string
value: string
public: boolean
}
export interface AuthProvider {
providerId: string
type: string
name: string
icon: string
}
export interface PaginatedResponse<T> {
items: T[]
total: number
page: number
pageSize: number
}
import type { ShareKind as _ShareKind } from '../schemas/share'
export type { ShareKind } from '../schemas/share'
export interface Share {
id: string
token: string
kind: _ShareKind
matterId: string
orgId: string
creatorId: string
passwordHash: string | null
expiresAt: Date | null
downloadLimit: number | null
views: number
downloads: number
status: 'active' | 'revoked'
createdAt: Date
}
export interface ShareRecipient {
id: string
shareId: string
recipientUserId: string | null
recipientEmail: string | null
createdAt: Date
}
export interface Notification {
id: string
userId: string
type: string
title: string
body: string
refType: string | null
refId: string | null
metadata: string | null
readAt: string | null
createdAt: string
}
export interface ActivityEvent {
id: string
orgId: string
userId: string
action: string
targetType: string
targetId: string | null
targetName: string
metadata: string | null
createdAt: string
user: {
id: string
name: string
image: string | null
}
}