mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-29 00:01:42 +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>
150 lines
2.6 KiB
TypeScript
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
|
|
}
|
|
}
|