diff --git a/src/types/src/api/requests.ts b/src/types/src/api/requests.ts index 5e40df0c..ebc0a6ec 100644 --- a/src/types/src/api/requests.ts +++ b/src/types/src/api/requests.ts @@ -142,3 +142,12 @@ export interface GetSimilarUrlsForUrlParams extends PaginatedSortedParams { url: string; threshold?: number; } + +// Notification request types +export interface GetMyNotificationsParams extends PaginatedSortedParams { + unreadOnly?: boolean; +} + +export interface MarkNotificationsAsReadRequest { + notificationIds: string[]; +} diff --git a/src/types/src/api/responses.ts b/src/types/src/api/responses.ts index f778eb8a..4e2e4071 100644 --- a/src/types/src/api/responses.ts +++ b/src/types/src/api/responses.ts @@ -241,3 +241,38 @@ export interface GetSimilarUrlsForUrlResponse { urls: UrlView[]; pagination: Pagination; } + +// Notification types +export enum NotificationType { + USER_ADDED_YOUR_CARD = 'USER_ADDED_YOUR_CARD', + USER_ADDED_YOUR_BSKY_POST = 'USER_ADDED_YOUR_BSKY_POST', + USER_ADDED_YOUR_COLLECTION = 'USER_ADDED_YOUR_COLLECTION', +} + +export interface NotificationItem { + id: string; + user: User; + card: UrlCard; + createdAt: string; + collections: Collection[]; + type: NotificationType; + read: boolean; +} + +export interface GetMyNotificationsResponse { + notifications: NotificationItem[]; + pagination: Pagination; + unreadCount: number; +} + +export interface GetUnreadNotificationCountResponse { + unreadCount: number; +} + +export interface MarkNotificationsAsReadResponse { + markedCount: number; +} + +export interface MarkAllNotificationsAsReadResponse { + markedCount: number; +} diff --git a/src/webapp/api-client/ApiClient.ts b/src/webapp/api-client/ApiClient.ts index f39e4a06..a74b14bb 100644 --- a/src/webapp/api-client/ApiClient.ts +++ b/src/webapp/api-client/ApiClient.ts @@ -4,6 +4,7 @@ import { CollectionClient, UserClient, FeedClient, + NotificationClient, } from './clients'; import type { // Request types @@ -64,6 +65,12 @@ import type { GetCollectionsForUrlResponse, GetSimilarUrlsForUrlParams, GetSimilarUrlsForUrlResponse, + GetMyNotificationsParams, + GetMyNotificationsResponse, + GetUnreadNotificationCountResponse, + MarkNotificationsAsReadRequest, + MarkNotificationsAsReadResponse, + MarkAllNotificationsAsReadResponse, } from '@semble/types'; // Main API Client class using composition @@ -73,6 +80,7 @@ export class ApiClient { private collectionClient: CollectionClient; private userClient: UserClient; private feedClient: FeedClient; + private notificationClient: NotificationClient; constructor(private baseUrl: string) { this.queryClient = new QueryClient(baseUrl); @@ -80,6 +88,7 @@ export class ApiClient { this.collectionClient = new CollectionClient(baseUrl); this.userClient = new UserClient(baseUrl); this.feedClient = new FeedClient(baseUrl); + this.notificationClient = new NotificationClient(baseUrl); } // Query operations - delegate to QueryClient @@ -273,6 +282,27 @@ export class ApiClient { ): Promise { return this.feedClient.getGlobalFeed(params); } + + // Notification operations - delegate to NotificationClient + async getMyNotifications( + params?: GetMyNotificationsParams, + ): Promise { + return this.notificationClient.getMyNotifications(params); + } + + async getUnreadNotificationCount(): Promise { + return this.notificationClient.getUnreadNotificationCount(); + } + + async markNotificationsAsRead( + request: MarkNotificationsAsReadRequest, + ): Promise { + return this.notificationClient.markNotificationsAsRead(request); + } + + async markAllNotificationsAsRead(): Promise { + return this.notificationClient.markAllNotificationsAsRead(); + } } // Re-export types for convenience diff --git a/src/webapp/api-client/clients/NotificationClient.ts b/src/webapp/api-client/clients/NotificationClient.ts new file mode 100644 index 00000000..6c76e9ba --- /dev/null +++ b/src/webapp/api-client/clients/NotificationClient.ts @@ -0,0 +1,53 @@ +import { BaseClient } from './BaseClient'; +import { + GetMyNotificationsParams, + MarkNotificationsAsReadRequest, + GetMyNotificationsResponse, + GetUnreadNotificationCountResponse, + MarkNotificationsAsReadResponse, + MarkAllNotificationsAsReadResponse, +} from '@semble/types'; + +export class NotificationClient extends BaseClient { + async getMyNotifications( + params?: GetMyNotificationsParams, + ): Promise { + const searchParams = new URLSearchParams(); + if (params?.page) searchParams.set('page', params.page.toString()); + if (params?.limit) searchParams.set('limit', params.limit.toString()); + if (params?.sortBy) searchParams.set('sortBy', params.sortBy); + if (params?.sortOrder) searchParams.set('sortOrder', params.sortOrder); + if (params?.unreadOnly) searchParams.set('unreadOnly', 'true'); + + const queryString = searchParams.toString(); + const endpoint = queryString + ? `/api/notifications?${queryString}` + : '/api/notifications'; + + return this.request('GET', endpoint); + } + + async getUnreadNotificationCount(): Promise { + return this.request( + 'GET', + '/api/notifications/unread-count', + ); + } + + async markNotificationsAsRead( + request: MarkNotificationsAsReadRequest, + ): Promise { + return this.request( + 'POST', + '/api/notifications/mark-read', + request, + ); + } + + async markAllNotificationsAsRead(): Promise { + return this.request( + 'POST', + '/api/notifications/mark-all-read', + ); + } +} diff --git a/src/webapp/api-client/clients/index.ts b/src/webapp/api-client/clients/index.ts index 210fa663..de19d1c9 100644 --- a/src/webapp/api-client/clients/index.ts +++ b/src/webapp/api-client/clients/index.ts @@ -4,3 +4,4 @@ export * from './CardClient'; export * from './CollectionClient'; export * from './UserClient'; export * from './FeedClient'; +export * from './NotificationClient';