notif cursor

This commit is contained in:
2025-12-21 12:34:45 +03:30
parent c36ff7ed85
commit ecba2773be
5 changed files with 72 additions and 155 deletions
@@ -35,11 +35,14 @@ export class ResponseInterceptor implements NestInterceptor {
}; };
} }
if (data && data.data !== undefined) { if (data && 'data' in data) {
return { return {
statusCode, statusCode,
success: true, success: true,
data: data.data, data: (data as any).data,
...('nextCursor' in (data as any) && (data as any).nextCursor != null
? { nextCursor: (data as any).nextCursor }
: {}),
}; };
} }
return { return {
@@ -24,7 +24,12 @@ export class NotificationsController {
@ApiOperation({ summary: 'Get user restaurant notifications' }) @ApiOperation({ summary: 'Get user restaurant notifications' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items to return (default: 50)' }) @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items to return (default: 50)' })
@ApiQuery({ name: 'cursor', required: false, type: String, description: 'Cursor for pagination (ID of the last item from previous page)' }) @ApiQuery({
name: 'cursor',
required: false,
type: String,
description: 'Cursor for pagination (ID of the last item from previous page)',
})
@ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' }) @ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' })
async getUserNotifications( async getUserNotifications(
@UserId() userId: string, @UserId() userId: string,
@@ -52,13 +57,29 @@ export class NotificationsController {
return { message: 'Notification read successfully' }; return { message: 'Notification read successfully' };
} }
/* ***************** Admin Endpoints ***************** */
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/notifications') @Get('admin/notifications')
@ApiOperation({ summary: 'Get Admin restaurant notifications' }) @ApiOperation({ summary: 'Get Admin notifications' })
@ApiQuery({ name: 'limit', required: false, type: Number }) @ApiQuery({ name: 'limit', required: false, type: Number })
async getAdminNotifications(@RestId() restaurantId: string, @Query('limit') limit?: number) { @ApiQuery({
return await this.notificationService.findByRestaurant(restaurantId, limit ? parseInt(limit.toString(), 30) : 50); name: 'cursor',
required: false,
type: String,
description: 'Cursor for pagination (ID of the last item from previous page)',
})
@ApiQuery({ name: 'status', required: false, enum: ['seen', 'unseen'], description: 'Filter by notification status' })
async getAdminNotifications(
@AdminId() adminId: string,
@RestId() restaurantId: string,
@Query('limit') limit?: number,
@Query('cursor') cursor?: string,
@Query('status') status?: 'seen' | 'unseen',
) {
const parsedLimit = limit ? parseInt(limit.toString(), 10) : 50;
return await this.notificationService.findByRestaurant(restaurantId, parsedLimit, cursor, status);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -71,72 +92,6 @@ export class NotificationsController {
return { message: 'Notification read successfully' }; return { message: 'Notification read successfully' };
} }
// @UseGuards(AuthGuard)
// @ApiBearerAuth()
// @Get('public/notifications/:id')
// @ApiOperation({ summary: 'Get a notification by ID' })
// @ApiParam({ name: 'id', description: 'Notification ID' })
// async getUserNotification(@Param('id') id: string) {
// return this.notificationService.findOne(id);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Post('admin/notifications/send')
// @ApiOperation({ summary: 'Send a notification (queued)' })
// async sendNotification(@Body() dto: SendNotificationDto) {
// return this.notificationService.sendNotification(dto);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Get('admin/notifications')
// @ApiOperation({ summary: 'Get notifications for a restaurant' })
// @ApiQuery({ name: 'limit', required: false, type: Number })
// @ApiQuery({ name: 'notificationType', required: false, type: String })
// async getRestaurantNotifications(
// @RestId() restaurantId: string,
// @Query('limit') limit?: number,
// @Query('notificationType') notificationType?: string,
// ) {
// if (notificationType) {
// return this.notificationService.findByRestaurantAndType(
// restaurantId,
// notificationType,
// limit ? parseInt(limit.toString(), 10) : 50,
// );
// }
// return this.notificationService.findByRestaurant(restaurantId, limit ? parseInt(limit.toString(), 10) : 50);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Get('admin/notifications/:id')
// @ApiOperation({ summary: 'Get a notification by ID' })
// @ApiParam({ name: 'id', description: 'Notification ID' })
// async getNotification(@Param('id') id: string) {
// return this.notificationService.findOne(id);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Post('admin/notifications/:id/retry')
// @ApiOperation({ summary: 'Retry a failed notification' })
// @ApiParam({ name: 'id', description: 'Notification ID' })
// async retryNotification(@Param('id') id: string) {
// await this.notificationService.retryFailedNotification(id);
// return { message: 'Notification queued for retry' };
// }
// Preference endpoints
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Post('admin/notification-preferences')
// @ApiOperation({ summary: 'Create notification preference' })
// createPreference(@RestId() restaurantId: string, @Body() dto: CreatePreferenceDto) {
// return this.preferenceService.createOrUpdate(restaurantId, dto);
// }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/notification-preferences') @Get('admin/notification-preferences')
@@ -145,28 +100,6 @@ export class NotificationsController {
return this.preferenceService.findByRestaurant(restaurantId); return this.preferenceService.findByRestaurant(restaurantId);
} }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Get('admin/notification-preferences/:type')
// @ApiOperation({ summary: 'Get a specific notification preference' })
// @ApiParam({ name: 'type', description: 'Notification type' })
// async getPreference(@RestId() restaurantId: string, @Param('type') notificationType: string) {
// return this.preferenceService.findByRestaurantAndType(restaurantId, notificationType);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Patch('admin/notification-preferences/:type/enabled')
// @ApiOperation({ summary: 'Enable or disable a notification preference' })
// @ApiParam({ name: 'type', description: 'Notification type' })
// async updatePreferenceEnabled(
// @RestId() restaurantId: string,
// @Param('type') notificationType: string,
// @Body('enabled') enabled: boolean,
// ) {
// return this.preferenceService.updateEnabled(restaurantId, notificationType, enabled);
// }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Patch('admin/notification-preferences/:id') @Patch('admin/notification-preferences/:id')
@@ -179,14 +112,4 @@ export class NotificationsController {
) { ) {
return this.preferenceService.updatePreference(preferenceId, restaurantId, dto); return this.preferenceService.updatePreference(preferenceId, restaurantId, dto);
} }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Delete('admin/notification-preferences/:id')
// @ApiOperation({ summary: 'Delete a notification preference' })
// @ApiParam({ name: 'id', description: 'Notification preference ID' })
// async deletePreference(@RestId() restaurantId: string, @Param('id') preferenceId: string) {
// await this.preferenceService.delete(restaurantId, preferenceId);
// return { message: 'Preference deleted successfully' };
// }
} }
@@ -96,16 +96,42 @@ export class NotificationService {
return notification; return notification;
} }
async findByRestaurant(restaurantId: string, limit = 50): Promise<Notification[]> { async findByRestaurant(
return this.em.find( restaurantId: string,
Notification, limit = 50,
{ restaurant: { id: restaurantId } }, cursor?: string,
{ status?: 'seen' | 'unseen',
orderBy: { createdAt: 'DESC' }, ): Promise<{ data: Notification[]; nextCursor: string | null }> {
limit, const where: FilterQuery<Notification> = {
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
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 next page
populate: ['user'], populate: ['user'],
}, });
);
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: nextCursor ?? null,
};
} }
async findByUserAndRestaurant( async findByUserAndRestaurant(
@@ -144,7 +170,7 @@ export class NotificationService {
return { return {
data, data,
nextCursor, nextCursor: nextCursor ?? null,
}; };
} }
@@ -63,40 +63,8 @@ export class PaymentsController {
verifyPayment(@Body() verifyPaymentDto: VerifyPaymentDto) { verifyPayment(@Body() verifyPaymentDto: VerifyPaymentDto) {
return this.paymentsService.verifyPayment(verifyPaymentDto.authority, verifyPaymentDto.orderId); return this.paymentsService.verifyPayment(verifyPaymentDto.authority, verifyPaymentDto.orderId);
} }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Get('admin/payments/:id')
// @ApiOperation({ summary: 'Get a payment by ID' })
// @ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
// @ApiOkResponse({ description: 'Payment found' })
// @ApiNotFoundResponse({ description: 'Payment not found' })
// findOnePayment(@Param('id') id: string) {
// return this.paymentsService.findById(+id);
// }
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @Patch('admin/payments/:id')
// @ApiOperation({ summary: 'Update a payment' })
// @ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
// @ApiBody({ type: UpdatePaymentDto })
// @ApiOkResponse({ description: 'Payment updated successfully' })
// @ApiNotFoundResponse({ description: 'Payment not found' })
// updatePayment(@Param('id') id: string, @Body() updatePaymentDto: UpdatePaymentDto) {
// return this.paymentsService.update(+id, updatePaymentDto);
// }
// @UseGuards(AdminAuthGuard) /** admin routes */
// @ApiBearerAuth()
// @Delete('admin/payments/:id')
// @ApiOperation({ summary: 'Delete a payment' })
// @ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
// @ApiOkResponse({ description: 'Payment deleted successfully' })
// @ApiNotFoundResponse({ description: 'Payment not found' })
// removePayment(@Param('id') id: string) {
// return this.paymentsService.remove(+id);
// }
/** payment methods */
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/payments/methods') @Get('admin/payments/methods')
@@ -7,9 +7,7 @@ import { Logger } from '@nestjs/common';
import { GatewayManager } from '../gateways/gateway.manager'; import { GatewayManager } from '../gateways/gateway.manager';
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity'; import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
import { OrderPaymentContext } from '../interface/payment'; import { OrderPaymentContext } from '../interface/payment';
import { InventoryService } from 'src/modules/inventory/inventory.service';
import { OrderStatus } from 'src/modules/orders/interface/order.interface'; import { OrderStatus } from 'src/modules/orders/interface/order.interface';
import { PaymentMethod } from '../entities/payment-method.entity';
@Injectable() @Injectable()
export class PaymentsService { export class PaymentsService {
@@ -18,7 +16,6 @@ export class PaymentsService {
constructor( constructor(
private readonly em: EntityManager, private readonly em: EntityManager,
private readonly gatewayManager: GatewayManager, private readonly gatewayManager: GatewayManager,
private readonly inventoryService: InventoryService,
) {} ) {}
async payOrder(orderId: string): Promise<{ paymentUrl: string | null }> { async payOrder(orderId: string): Promise<{ paymentUrl: string | null }> {