notif cursor
This commit is contained in:
@@ -5,7 +5,7 @@ import { map } from 'rxjs/operators';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResponseInterceptor implements NestInterceptor {
|
export class ResponseInterceptor implements NestInterceptor {
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<Record<string, unknown>>): Observable<unknown> {
|
intercept(context: ExecutionContext, next: CallHandler<Record<string, unknown>>): Observable<unknown> {
|
||||||
// For REST endpoints, wrap the response
|
// For REST endpoints, wrap the response
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class NotificationsController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private readonly preferenceService: NotificationPreferenceService,
|
private readonly preferenceService: NotificationPreferenceService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -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' };
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class NotificationService {
|
|||||||
private readonly preferenceService: NotificationPreferenceService,
|
private readonly preferenceService: NotificationPreferenceService,
|
||||||
private readonly queueService: NotificationQueueService,
|
private readonly queueService: NotificationQueueService,
|
||||||
private readonly notificationGateway: NotificationsGateway,
|
private readonly notificationGateway: NotificationsGateway,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async sendNotification(params: NotifRequest): Promise<Notification[]> {
|
async sendNotification(params: NotifRequest): Promise<Notification[]> {
|
||||||
const { recipients, message, metadata, restaurantId } = params;
|
const { recipients, message, metadata, restaurantId } = params;
|
||||||
@@ -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> = {
|
||||||
populate: ['user'],
|
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'],
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class PaymentsController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly paymentsService: PaymentsService,
|
private readonly paymentsService: PaymentsService,
|
||||||
private readonly paymentMethodService: PaymentMethodService,
|
private readonly paymentMethodService: PaymentMethodService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -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 }> {
|
||||||
|
|||||||
Reference in New Issue
Block a user