diff --git a/src/public/dto/subscribe.dto.ts b/src/public/dto/subscribe.dto.ts new file mode 100644 index 0000000..44c6871 --- /dev/null +++ b/src/public/dto/subscribe.dto.ts @@ -0,0 +1,21 @@ +import { Transform } from 'class-transformer'; +import { IsString, Matches } from 'class-validator'; + +const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹'; +const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩'; + +export function normalizeCellNumber(value: unknown) { + if (typeof value !== 'string') return value; + return value + .trim() + .replace(/[۰-۹]/g, (digit) => String(PERSIAN_DIGITS.indexOf(digit))) + .replace(/[٠-٩]/g, (digit) => String(ARABIC_DIGITS.indexOf(digit))) + .replace(/[^\d]/g, ''); +} + +export class SubscribeDto { + @Transform(({ value }) => normalizeCellNumber(value)) + @IsString() + @Matches(/^09\d{9}$/, { message: 'شماره موبایل معتبر نیست' }) + cellNumber!: string; +} diff --git a/src/public/public.controller.ts b/src/public/public.controller.ts index 610b964..6a45b55 100644 --- a/src/public/public.controller.ts +++ b/src/public/public.controller.ts @@ -1,4 +1,5 @@ -import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { SubscribeDto } from './dto/subscribe.dto'; import { PublicService } from './public.service'; @Controller('public') @@ -78,4 +79,9 @@ export class PublicController { listWebsiteSlots(@Query('storeSlug') storeSlug?: string) { return this.publicService.listWebsiteSlots(storeSlug); } + + @Post('subscribe') + subscribe(@Body() dto: SubscribeDto) { + return this.publicService.subscribe(dto.cellNumber); + } } diff --git a/src/public/public.service.ts b/src/public/public.service.ts index 74fd323..3b5e790 100644 --- a/src/public/public.service.ts +++ b/src/public/public.service.ts @@ -1,5 +1,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; -import { Prisma, StoreStatus } from '@prisma/client'; +import { Prisma, StoreStatus, UserRole } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; +import { randomBytes } from 'crypto'; import { PrismaService } from '../prisma/prisma.service'; export type PublicCategoryTreeNode = { @@ -267,4 +269,39 @@ export class PublicService { return { storeSlug: store.slug, items }; } + + async subscribe(cellNumber: string) { + const existing = await this.prisma.user.findUnique({ + where: { cellNumber }, + select: { id: true }, + }); + if (existing) { + return { ok: true as const, created: false }; + } + + const passwordHash = await bcrypt.hash(randomBytes(32).toString('hex'), 10); + + try { + await this.prisma.user.create({ + data: { + firstName: 'مشترک', + lastName: 'خبرنامه', + cellNumber, + passwordHash, + role: UserRole.CUSTOMER, + about: 'ثبت‌نام از پنجره اشتراک صفحه اصلی', + }, + }); + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2002' + ) { + return { ok: true as const, created: false }; + } + throw error; + } + + return { ok: true as const, created: true }; + } }