update notif , now it is cursor based
This commit is contained in:
@@ -8,6 +8,7 @@ import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
|||||||
import { RestId } from '../../../common/decorators/rest-id.decorator';
|
import { RestId } from '../../../common/decorators/rest-id.decorator';
|
||||||
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
|
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
|
||||||
import { AdminId } from 'src/common/decorators/admin-id.decorator';
|
import { AdminId } from 'src/common/decorators/admin-id.decorator';
|
||||||
|
import { API_HEADER_SLUG } from 'src/common/constants/index';
|
||||||
|
|
||||||
@ApiTags('notifications')
|
@ApiTags('notifications')
|
||||||
@Controller()
|
@Controller()
|
||||||
@@ -21,20 +22,23 @@ export class NotificationsController {
|
|||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Get('public/notifications')
|
@Get('public/notifications')
|
||||||
@ApiOperation({ summary: 'Get user restaurant notifications' })
|
@ApiOperation({ summary: 'Get user restaurant notifications' })
|
||||||
@ApiHeader({
|
@ApiHeader(API_HEADER_SLUG)
|
||||||
name: 'X-Slug',
|
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items to return (default: 50)' })
|
||||||
required: true,
|
@ApiQuery({ name: 'cursor', required: false, type: String, description: 'Cursor for pagination (ID of the last item from previous page)' })
|
||||||
schema: {
|
@ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' })
|
||||||
type: 'string',
|
async getUserNotifications(
|
||||||
default: 'zhivan',
|
@UserId() userId: string,
|
||||||
},
|
@RestId() restaurantId: string,
|
||||||
})
|
@Query('limit') limit?: number,
|
||||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
@Query('cursor') cursor?: string,
|
||||||
async getUserNotifications(@UserId() userId: string, @RestId() restaurantId: string, @Query('limit') limit?: number) {
|
@Query('status') status?: 'seen' | 'unseen',
|
||||||
|
) {
|
||||||
return await this.notificationService.findByUserAndRestaurant(
|
return await this.notificationService.findByUserAndRestaurant(
|
||||||
userId,
|
userId,
|
||||||
restaurantId,
|
restaurantId,
|
||||||
limit ? parseInt(limit.toString(), 10) : 50,
|
limit ? parseInt(limit.toString(), 10) : 50,
|
||||||
|
cursor,
|
||||||
|
status,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ export class Notification extends BaseEntity {
|
|||||||
content!: string;
|
content!: string;
|
||||||
|
|
||||||
@Property({ nullable: true })
|
@Property({ nullable: true })
|
||||||
sentAt?: Date;
|
seenAt?: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
import { EntityManager, FilterQuery } from '@mikro-orm/postgresql';
|
||||||
import { Notification } from '../entities/notification.entity';
|
import { Notification } from '../entities/notification.entity';
|
||||||
import { NotificationPreferenceService } from './notification-preference.service';
|
import { NotificationPreferenceService } from './notification-preference.service';
|
||||||
import { NotificationQueueService } from './notification-queue.service';
|
import { NotificationQueueService } from './notification-queue.service';
|
||||||
@@ -108,15 +108,44 @@ export class NotificationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByUserAndRestaurant(userId: string, restaurantId: string, limit = 50): Promise<Notification[]> {
|
async findByUserAndRestaurant(
|
||||||
return this.em.find(
|
userId: string,
|
||||||
Notification,
|
restaurantId: string,
|
||||||
{ user: { id: userId }, restaurant: { id: restaurantId } },
|
limit = 50,
|
||||||
{
|
cursor?: string,
|
||||||
orderBy: { createdAt: 'DESC' },
|
status?: 'seen' | 'unseen',
|
||||||
limit,
|
): Promise<{ data: Notification[]; nextCursor: string | null }> {
|
||||||
},
|
const where: FilterQuery<Notification> = {
|
||||||
);
|
user: { id: userId },
|
||||||
|
restaurant: { id: restaurantId },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter by status (seen/unseen)
|
||||||
|
if (status === 'seen') {
|
||||||
|
where.seenAt = { $ne: null };
|
||||||
|
} else if (status === 'unseen') {
|
||||||
|
where.seenAt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor-based pagination: if cursor is provided, get items with id < cursor (since ULIDs are time-ordered)
|
||||||
|
if (cursor) {
|
||||||
|
where.id = { $lt: cursor };
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = await this.em.find(Notification, where, {
|
||||||
|
orderBy: { createdAt: 'DESC', id: 'DESC' },
|
||||||
|
limit: limit + 1, // Fetch one extra to determine if there's a next page
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there's a next page
|
||||||
|
const hasNextPage = notifications.length > limit;
|
||||||
|
const data = hasNextPage ? notifications.slice(0, limit) : notifications;
|
||||||
|
const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
nextCursor,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async readNotificationAdmin(id: string, adminId: string, restaurantId: string): Promise<void> {
|
async readNotificationAdmin(id: string, adminId: string, restaurantId: string): Promise<void> {
|
||||||
@@ -128,7 +157,7 @@ export class NotificationService {
|
|||||||
if (!notification) {
|
if (!notification) {
|
||||||
throw new NotFoundException('Notification not found');
|
throw new NotFoundException('Notification not found');
|
||||||
}
|
}
|
||||||
notification.sentAt = new Date();
|
notification.seenAt = new Date();
|
||||||
await this.em.persistAndFlush(notification);
|
await this.em.persistAndFlush(notification);
|
||||||
}
|
}
|
||||||
async readNotificationAsUser(id: string, userId: string, restaurantId: string): Promise<void> {
|
async readNotificationAsUser(id: string, userId: string, restaurantId: string): Promise<void> {
|
||||||
@@ -140,7 +169,7 @@ export class NotificationService {
|
|||||||
if (!notification) {
|
if (!notification) {
|
||||||
throw new NotFoundException('Notification not found');
|
throw new NotFoundException('Notification not found');
|
||||||
}
|
}
|
||||||
notification.sentAt = new Date();
|
notification.seenAt = new Date();
|
||||||
await this.em.persistAndFlush(notification);
|
await this.em.persistAndFlush(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user