Initialize the API and add managed app banners.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
||||
import { IsString, MinLength } from 'class-validator';
|
||||
import { StockNotifyService } from './stock-notify.service';
|
||||
import { AuthType } from '../common/decorators/auth.decorators';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
|
||||
|
||||
class SubscribeDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
productId!: string;
|
||||
}
|
||||
|
||||
@Controller()
|
||||
export class StockNotifyController {
|
||||
constructor(private readonly stockNotifyService: StockNotifyService) {}
|
||||
|
||||
@AuthType('admin')
|
||||
@Get('admin/stock-alerts')
|
||||
list(@Query('activeOnly') activeOnly?: string) {
|
||||
return this.stockNotifyService.listAdmin(activeOnly !== 'false');
|
||||
}
|
||||
|
||||
@AuthType('admin')
|
||||
@Patch('admin/stock-alerts/:id/notify')
|
||||
notify(@Param('id') id: string) {
|
||||
return this.stockNotifyService.markNotified(id);
|
||||
}
|
||||
|
||||
@AuthType('customer')
|
||||
@Post('stock-alerts')
|
||||
subscribe(@CurrentUser() user: AuthPrincipal, @Body() dto: SubscribeDto) {
|
||||
return this.stockNotifyService.subscribe(user.id, dto.productId);
|
||||
}
|
||||
|
||||
@AuthType('customer')
|
||||
@Post('stock-alerts/:productId/cancel')
|
||||
unsubscribe(
|
||||
@CurrentUser() user: AuthPrincipal,
|
||||
@Param('productId') productId: string,
|
||||
) {
|
||||
return this.stockNotifyService.unsubscribe(user.id, productId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { StockNotifyController } from './stock-notify.controller';
|
||||
import { StockNotifyService } from './stock-notify.service';
|
||||
|
||||
@Module({
|
||||
controllers: [StockNotifyController],
|
||||
providers: [StockNotifyService],
|
||||
exports: [StockNotifyService],
|
||||
})
|
||||
export class StockNotifyModule {}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class StockNotifyService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async listAdmin(activeOnly = true) {
|
||||
const rows = await this.prisma.stockNotifyRequest.findMany({
|
||||
where: activeOnly ? { active: true } : undefined,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } },
|
||||
product: { select: { name: true, code: true, color: true, stock: true } },
|
||||
},
|
||||
});
|
||||
return rows.map((r) => this.map(r));
|
||||
}
|
||||
|
||||
async subscribe(customerId: string, productId: string) {
|
||||
const product = await this.prisma.product.findUnique({ where: { id: productId } });
|
||||
if (!product) throw new NotFoundException('محصول یافت نشد');
|
||||
const row = await this.prisma.stockNotifyRequest.upsert({
|
||||
where: { customerId_productId: { customerId, productId } },
|
||||
create: { customerId, productId, active: true },
|
||||
update: { active: true, notifiedAt: null },
|
||||
include: {
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } },
|
||||
product: { select: { name: true, code: true, color: true, stock: true } },
|
||||
},
|
||||
});
|
||||
return this.map(row);
|
||||
}
|
||||
|
||||
async unsubscribe(customerId: string, productId: string) {
|
||||
await this.prisma.stockNotifyRequest.updateMany({
|
||||
where: { customerId, productId },
|
||||
data: { active: false },
|
||||
});
|
||||
return { productId, active: false };
|
||||
}
|
||||
|
||||
async markNotified(id: string) {
|
||||
const existing = await this.prisma.stockNotifyRequest.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('درخواست یافت نشد');
|
||||
const row = await this.prisma.stockNotifyRequest.update({
|
||||
where: { id },
|
||||
data: { notifiedAt: new Date(), active: false },
|
||||
include: {
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } },
|
||||
product: { select: { name: true, code: true, color: true, stock: true } },
|
||||
},
|
||||
});
|
||||
|
||||
await this.prisma.customerNotification.create({
|
||||
data: {
|
||||
customerId: row.customerId,
|
||||
event: 'stock_available',
|
||||
title: 'موجودی محصول',
|
||||
body: `محصول ${row.product.name} دوباره موجود شد.`,
|
||||
tone: 'success',
|
||||
link: `/product?id=${row.productId}`,
|
||||
},
|
||||
});
|
||||
|
||||
return this.map(row);
|
||||
}
|
||||
|
||||
private map(r: {
|
||||
id: string;
|
||||
customerId: string;
|
||||
productId: string;
|
||||
active: boolean;
|
||||
createdAt: Date;
|
||||
notifiedAt: Date | null;
|
||||
customer: { firstName: string; lastName: string; phone: string };
|
||||
product: { name: string; code: string; color: string; stock: number | null };
|
||||
}) {
|
||||
return {
|
||||
id: r.id,
|
||||
customerId: r.customerId,
|
||||
customerName: `${r.customer.firstName} ${r.customer.lastName}`.trim(),
|
||||
customerPhone: r.customer.phone,
|
||||
productId: r.productId,
|
||||
productName: r.product.name,
|
||||
productCode: r.product.code,
|
||||
color: r.product.color,
|
||||
stock: r.product.stock,
|
||||
active: r.active,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
notifiedAt: r.notifiedAt?.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user