diff --git a/package-lock.json b/package-lock.json index ec49305..f5f10e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,10 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", + "@types/kavenegar": "^1.1.3", "@types/passport-google-oauth20": "^2.0.16", "aws-sdk": "^2.1692.0", + "axios": "^1.10.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "dotenv": "^17.0.0", @@ -5380,6 +5382,12 @@ "@types/node": "*" } }, + "node_modules/@types/kavenegar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/kavenegar/-/kavenegar-1.1.3.tgz", + "integrity": "sha512-va7j5bjW+ic1rmTH2kLOItgGflZ/ZI4G7rPQAo+Ysiccb0mFQhK5Dz/HvdNCMZ1TlAQNWb+QCfBsPaktqwYjuA==", + "license": "MIT" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -6838,7 +6846,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -6913,6 +6920,17 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -7818,7 +7836,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -8162,7 +8179,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -8419,7 +8435,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9173,6 +9188,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -9237,7 +9272,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -9264,7 +9298,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9274,7 +9307,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -12432,6 +12464,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 02bae61..9a86a44 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", + "@types/kavenegar": "^1.1.3", "@types/passport-google-oauth20": "^2.0.16", "aws-sdk": "^2.1692.0", + "axios": "^1.10.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "dotenv": "^17.0.0", diff --git a/src/app.module.ts b/src/app.module.ts index 4b450a9..f64378c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,13 +13,16 @@ import { ConfigModule } from '@nestjs/config'; import { ProductModule } from './product/product.module'; import { DiscountModule } from './discount/discount.module'; import { DiscountTypeModule } from './discount-type/discount-type.module'; +import { SmsModule } from './sms/sms.module'; +import { UserLoginModule } from './user-login/user-login.module'; +import { UserModule } from './user/user.module'; @Module({ imports: [ CategoryModule,UploadModule,LoginModule,ShopModule,ProductModule,DiscountModule, MongooseModule.forRoot('mongodb://root:FcpaOAxvZd2IiqK5Vl5QRMTE@fartakdatabase:27017/my-app?authSource=admin'), - ConfigModule.forRoot({ isGlobal: true }), DiscountTypeModule + ConfigModule.forRoot({ isGlobal: true }), DiscountTypeModule, SmsModule, UserLoginModule, UserModule ], controllers: [AppController], providers: [AppService], diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 8fddd7c..69e5a53 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -2,10 +2,12 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { JwtService } from '@nestjs/jwt'; import { Response, Request } from 'express'; - +import { JwtAuthGuard } from './jwt-auth.guard'; +import { AuthenticatedRequest } from 'src/interfaces/request'; +import { AuthService } from './auth.service'; @Controller('auth') export class AuthController { - constructor(private jwtService: JwtService) {} + constructor(private jwtService: JwtService,private readonly authService:AuthService) {} @Get('google') @UseGuards(AuthGuard('google')) @@ -34,4 +36,17 @@ export class AuthController { // ✅ Option 2: Redirect to frontend with token res.redirect(`http://localhost:4200/login/success?token=${token}`); } + + + @UseGuards(JwtAuthGuard) + @Get('/get') + async getListDiscountShop( + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + const createdShop = await this.authService.verifyauth(req.user.user_ID); + console.log(createdShop) + return res.status(createdShop.status).json(createdShop.data); + } + } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 33b7445..640662f 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -5,11 +5,13 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { AuthService } from './auth.service'; import { JwtStrategy } from './jwt.strategy'; import { AuthController } from './auth.controller'; +import { SellerModule } from 'src/seller/seller.module'; @Module({ imports: [ ConfigModule, PassportModule, + SellerModule, JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ff0a9da..40a4a79 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,21 +1,23 @@ // auth.service.ts import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { CreateSellerResponse } from 'src/interfaces/request'; +import { SellerService } from 'src/seller/seller.service'; @Injectable() export class AuthService { - constructor(private jwtService: JwtService) {} + constructor(private jwtService: JwtService,private readonly sellerService:SellerService) {} async generateTokens(payload:any) { console.log(process.env.JWT_SECRET) const accessToken = await this.jwtService.signAsync(payload, { secret: process.env.JWT_SECRET, - expiresIn: '15m', + expiresIn: '1d', }); const refreshToken = await this.jwtService.signAsync(payload, { secret: process.env.JWT_REFRESH_SECRET, - expiresIn: '7d', + expiresIn: '30d', }); return { @@ -24,9 +26,9 @@ export class AuthService { }; } - async verifyRefreshToken(token: string) { + async verifyToken(token: string) { return this.jwtService.verifyAsync(token, { - secret: process.env.JWT_REFRESH_SECRET, + secret: process.env.JWT_SECRET, }); } async loginWithGoogle(user: any) { @@ -37,5 +39,25 @@ export class AuthService { }; } + async verifyauth(user: string):Promise { + const dataSeller = await this.sellerService.findSellerwidtID(user) + console.log("data") + console.log(dataSeller) + if(dataSeller){ + return { + "message":"فروشنده موجود است", + "status":200, + "data":dataSeller + } + }else{ + return { + "message":"فروشنده موجود نیست", + "status":404, + "data":null + } + } + + } + } diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts index 677b108..6b24ce1 100644 --- a/src/auth/jwt.strategy.ts +++ b/src/auth/jwt.strategy.ts @@ -1,21 +1,27 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable,UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { ConfigService } from '@nestjs/config'; - +import { UserService } from 'src/user/user.service'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private configService: ConfigService) { + constructor(private configService: ConfigService,private readonly userService:UserService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: configService.get('JWT_REFRESH_SECRET'), + secretOrKey: configService.get('JWT_SECRET'), }); } async validate(payload: any) { -console.log("payload") + console.log("payload") console.log(payload) - return { userId: payload.sellerid}; + const user = await this.userService.findUserwidtID(payload.userID) + console.log("user") + console.log(user) + if (user.status != 200) { + throw new UnauthorizedException('کاربر وارد نشده است'); + } + return { userId: user.data._id,user_ID: user.data.ID}; } } diff --git a/src/category/category.controller.ts b/src/category/category.controller.ts index a6d6ffc..ff54a64 100644 --- a/src/category/category.controller.ts +++ b/src/category/category.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Res, Post } from '@nestjs/common'; +import { Body, Controller, Res, Post, Get } from '@nestjs/common'; import { Response } from 'express'; import { CategoryService } from './category.service'; import { CreatCategoryDto } from './dto/creatCategory.dto'; @@ -12,4 +12,10 @@ export class CategoryController { const result = await this.categoryService.creatCategory(dto); return res.status(result.status).json(result); } + + @Get('/all') + async findAll(@Res() res: Response) { + const result = await this.categoryService.findAllCategory(); + return res.status(result.status).json(result); + } } diff --git a/src/category/category.service.ts b/src/category/category.service.ts index 9aa879c..74a4fd3 100644 --- a/src/category/category.service.ts +++ b/src/category/category.service.ts @@ -24,7 +24,8 @@ export class CategoryService { const newCategory = new this.categoryModel({ Name:creatCategory.Name, Description:creatCategory.Description, - ID:newUuid + ID:newUuid, + Code:creatCategory.Code, }) const result = newCategory.save() @@ -63,6 +64,14 @@ export class CategoryService { } } } + async findAllCategory() :Promise { + const categories = await this.categoryModel.find().select('-_id') + return { + "message":"همه دسته بندی ها", + "status":200, + "data":categories + } + } } diff --git a/src/category/dto/creatCategory.dto.ts b/src/category/dto/creatCategory.dto.ts index 649706a..56d86ea 100644 --- a/src/category/dto/creatCategory.dto.ts +++ b/src/category/dto/creatCategory.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from "class-validator"; +import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator"; export class CreatCategoryDto { @IsNotEmpty({message:"نام دسته بندی نباید خالی باشد . "}) @@ -9,6 +9,13 @@ export class CreatCategoryDto { @IsString({message:"نام دسته بندی باید متن باشد . "}) Description:string + @IsNotEmpty({ message: 'کد دسته بندی نمی‌تواند خالی باشد.' }) + @IsNumberString({},{ message: 'کد دسته بندی باید فقط شامل اعداد باشد.' }) + Code: number; + + + + } diff --git a/src/discount-type/discount-type.controller.ts b/src/discount-type/discount-type.controller.ts index 3a910b4..7ffab6d 100644 --- a/src/discount-type/discount-type.controller.ts +++ b/src/discount-type/discount-type.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Res, Post } from '@nestjs/common'; +import { Body, Controller, Res, Post,Get} from '@nestjs/common'; import { Response } from 'express'; import { DiscountTypeService } from './discount-type.service'; import { CreatDiscountTypeDto } from './dto/discountType.dto'; @@ -12,4 +12,10 @@ export class DiscountTypeController { const result = await this.discountTypeService.creatDiscountType(dto); return res.status(result.status).json(result); } + + @Get('/all') + async findAll(@Res() res: Response) { + const result = await this.discountTypeService.findAllDiscountType(); + return res.status(result.status).json(result); + } } diff --git a/src/discount-type/discount-type.service.ts b/src/discount-type/discount-type.service.ts index d9e4f73..18a3696 100644 --- a/src/discount-type/discount-type.service.ts +++ b/src/discount-type/discount-type.service.ts @@ -20,7 +20,8 @@ export class DiscountTypeService { const newDiscountType = new this.discountTypeModel({ Name:creatDiscountType.Name, Description:creatDiscountType.Description, - ID:newUuid + ID:newUuid, + Code:creatDiscountType.Code }) const result = newDiscountType.save() @@ -59,5 +60,15 @@ export class DiscountTypeService { } } + async findAllDiscountType() :Promise { + const categories = await this.discountTypeModel.find().select('-_id') + return { + "message":"همه دسته بندی های تخفیف", + "status":200, + "data":categories + } + } + + } diff --git a/src/discount-type/dto/discountType.dto.ts b/src/discount-type/dto/discountType.dto.ts index 197d249..a7dd10b 100644 --- a/src/discount-type/dto/discountType.dto.ts +++ b/src/discount-type/dto/discountType.dto.ts @@ -1,14 +1,19 @@ -import { IsNotEmpty, IsString } from "class-validator"; +import { IsNotEmpty, IsString,IsNumberString } from "class-validator"; export class CreatDiscountTypeDto { - @IsNotEmpty({message:"نام دسته بندی نباید خالی باشد . "}) - @IsString({message:"نام دسته بندی باید متن باشد . "}) + @IsNotEmpty({message:"نوع تخفیف نباید خالی باشد . "}) + @IsString({message:"نوع تخفیف باید متن باشد . "}) Name:string; - @IsNotEmpty({message:"نام دسته بندی نباید خالی باشد . "}) - @IsString({message:"نام دسته بندی باید متن باشد . "}) + @IsNotEmpty({message:"نوع تخفیف نباید خالی باشد . "}) + @IsString({message:"نوع تخفیف باید متن باشد . "}) Description:string + @IsNotEmpty({ message: 'نوع تخفیف نمی‌تواند خالی باشد.' }) + @IsNumberString({},{ message: 'نوع تخفیف باید فقط شامل اعداد باشد.' }) + Code: number; + + } diff --git a/src/discount/discount.controller.ts b/src/discount/discount.controller.ts index a6fee23..3abbdc6 100644 --- a/src/discount/discount.controller.ts +++ b/src/discount/discount.controller.ts @@ -5,28 +5,139 @@ import { UseGuards, Res, Req, - + UploadedFiles, + BadRequestException, + UseInterceptors, + Param, + Get, + Query } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; +import { FilesInterceptor } from '@nestjs/platform-express'; import { DiscountService } from './discount.service'; import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; import { AuthenticatedRequest } from 'src/interfaces/request'; import { CreateDiscountDto } from './dto/discount.dto'; +import { EditDiscountDto } from './dto/editDiscount.dto'; +import { UploadService } from 'src/upload/upload.service'; +import { PaginationQueryDiscountDto } from './dto/discountfind.dto'; +import { Request } from 'express'; import { Response } from 'express'; @Controller('discount') export class DiscountController { constructor( + private readonly upload: UploadService, private readonly discountService: DiscountService, ) {} - @UseGuards(JwtAuthGuard) - @Post('/add') + @UseGuards(JwtAuthGuard) + @Post('/add') + @UseInterceptors( + FilesInterceptor('Images', 3, { + limits: { + fileSize: 2 * 1024 * 1024, // 2MB + }, + fileFilter: (req, file, cb) => { + const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg']; + cb(null, true); + }, + })) async create( @Body() dto:CreateDiscountDto, + @UploadedFiles() files: Express.Multer.File[], @Req() req: AuthenticatedRequest, @Res() res: Response, ) { - const creatDiscount = await this.discountService.creatDiscount(dto,req.user.userId); + let fileIds: string[] = []; + + if (files && files.length > 0) { + for (const file of files) { + const result = await this.upload.uploadFileSingle(file); + if (result.status !== 200) { + return res.status(400).json({ + message: 'آپلود فایل با خطا مواجه شد', + status: 400, + }); + } + fileIds.push(result.data._id); + } + } + + const creatDiscount = await this.discountService.creatDiscount(dto,fileIds,req.user.userId); return res.status(creatDiscount.status).json(creatDiscount); } + + @UseGuards(JwtAuthGuard) + @Get('/get') + async getListDiscountShop( + @Query() query :PaginationQueryDiscountDto, + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + const createdShop = await this.discountService.findAlldiscount(req.user.userId,query); + return res.status(createdShop.status).json(createdShop); + } + + @UseGuards(JwtAuthGuard) + @Get('/get/:id') + async getSingleDiscountwithID( + @Param('id') id: string, + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + const createdShop = await this.discountService.findDiscountWithID(id); + return res.status(createdShop.status).json(createdShop); + } + + + @UseGuards(JwtAuthGuard) + @Get('/delete/:id') + async deleteSingleDiscountwithID( + @Param('id') id: string, + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + const createdShop = await this.discountService.deleteDiscountWithID(id); + return res.status(createdShop.status).json(createdShop); + } + + @UseGuards(JwtAuthGuard) + @Post('/edit/:id') + @UseInterceptors( + FilesInterceptor('Images', 3, { + limits: { + fileSize: 2 * 1024 * 1024, // 2MB + }, + fileFilter: (req, file, cb) => { + const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg']; + cb(null, true); + }, + })) + async editDiscountWithID( + @Param('id') id: string, + @Body() dto:EditDiscountDto, + @UploadedFiles() files: Express.Multer.File[], + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + let fileIds: string[] = []; + + if (files && files.length > 0) { + for (const file of files) { + const result = await this.upload.uploadFileSingle(file); + if (result.status !== 200) { + return res.status(400).json({ + message: 'آپلود فایل با خطا مواجه شد', + status: 400, + }); + } + fileIds.push(result.data._id); + } + } + const createdShop = await this.discountService.editDiscount(id,dto,fileIds,req.user.userId); + return res.status(createdShop.status).json(createdShop); + + + } + } diff --git a/src/discount/discount.module.ts b/src/discount/discount.module.ts index 6fd53dc..245fa0b 100644 --- a/src/discount/discount.module.ts +++ b/src/discount/discount.module.ts @@ -6,11 +6,13 @@ import { DiscountSchema } from 'src/schemas/discount.schema'; import { ProductModule } from 'src/product/product.module'; import { ShopModule } from 'src/shop/shop.module'; import { DiscountTypeModule } from 'src/discount-type/discount-type.module'; +import { UploadModule } from 'src/upload/upload.module'; +import { UploadService } from 'src/upload/upload.service'; @Module({ imports:[ MongooseModule.forFeature([ { name: 'Discount', schema: DiscountSchema }, - ]),ProductModule,DiscountTypeModule,ShopModule + ]),UploadModule,DiscountTypeModule,ShopModule ], diff --git a/src/discount/discount.service.ts b/src/discount/discount.service.ts index 5f96533..0c4c18f 100644 --- a/src/discount/discount.service.ts +++ b/src/discount/discount.service.ts @@ -9,26 +9,32 @@ import { CreateDiscountDto } from './dto/discount.dto'; import { CreateSellerResponse } from 'src/interfaces/request'; import { v4 as uuidv4 } from 'uuid'; import { ShopService } from 'src/shop/shop.service'; - +import { UploadService } from 'src/upload/upload.service'; +import { PaginationQueryDiscountDto } from './dto/discountfind.dto'; +import { EditDiscountDto } from './dto/editDiscount.dto'; @Injectable() export class DiscountService { constructor( @InjectModel(Discount.name) private readonly discountModel: Model, private readonly shopService: ShopService, - private readonly productService: ProductService, private readonly discountTypeService: DiscountTypeService, - ) {} - + private readonly upload: UploadService + ) { } + private convertToMinutes(time: string): number { + const [hours, minutes] = time.split(':').map(Number); + return hours * 60 + minutes; + } async creatDiscount( creatDiscountDto: CreateDiscountDto, + images: (string[] | null), user: any, ): Promise { const newUuid: string = uuidv4(); console.log("creatDiscountDto") - console.log(creatDiscountDto) + console.log(creatDiscountDto) const existCategory = await this.discountTypeService.findDiscountType(creatDiscountDto.Type); if (existCategory.status !== 200 || !existCategory.data) { return { @@ -38,7 +44,7 @@ export class DiscountService { }; } - const existShop = await this.shopService.findShopSingle(creatDiscountDto.Shop); + const existShop = await this.shopService.findShopWithSeller(user); if (existShop.status !== 200 || !existShop.data) { return { message: 'فروشگاه ثبت نشده است', @@ -46,50 +52,43 @@ export class DiscountService { data: null, }; } - - if (!existShop.data.Seller) { - return { - message: 'اطلاعات فروشنده فروشگاه ناقص است', - status: 500, - data: null, - }; - } - const sellerId = new Types.ObjectId(user); - const shopSellerId = existShop.data.Seller; - - if (!sellerId.equals(shopSellerId)) { + const start = this.convertToMinutes(creatDiscountDto.StartTime); + const end = this.convertToMinutes(creatDiscountDto.EndTime); + if (end <= start) { return { - message: 'این فروشنده مجاز به ثبت تخفیف برای این فروشگاه نیست', - status: 403, - data: null, - }; - } - - const existProduct = await this.productService.findProductSingle(creatDiscountDto.Product); - if (existProduct.status !== 200 || !existProduct.data) { - return { - message: 'محصول ثبت نشده است', + message: 'تایم شروع نباید از تایم پایان بزرگتر باشد', status: 404, data: null, }; } - if (!existProduct.data.Shop.equals(existShop.data._id)) { + + const sellerId = new Types.ObjectId(user); + const shopSellerId = existShop.data.Seller._id; + + console.log("shopSellerId") + console.log(existShop) + /*if (!sellerId.equals(shopSellerId)) { return { - message: 'این محصول در این فروشگاه ثبت نشده است', - status: 400, + message: 'این فروشنده مجاز به ثبت تخفیف برای این فروشگاه نیست', + status: 403, data: null, }; } +*/ try { const newDiscount = new this.discountModel({ + Name: creatDiscountDto.Name, Shop: existShop.data._id, - Product: existProduct.data._id, + Images: images, Type: existCategory.data._id, + ProductDescription: "محصول جدید", Description: creatDiscountDto.Description, + Price: creatDiscountDto.Price, + NPrice: creatDiscountDto.NPrice, StartDate: creatDiscountDto.Start, EndDate: creatDiscountDto.End, StartTime: creatDiscountDto.StartTime, @@ -116,4 +115,204 @@ export class DiscountService { }; } } + + async findAlldiscount( + user: any, + query: PaginationQueryDiscountDto, + ): Promise { + const existShop = await this.shopService.findShopWithSeller(user); + + if (existShop.status !== 200 || !existShop.data) { + return { + message: 'فروشگاهی ثبت نشده است', + status: 404, + data: null, + }; + } + + const filter: any = {}; + const now = new Date(); + + + filter.Shop = existShop.data._id; + + + if (query.search) { + filter.Name = { $regex: query.search, $options: 'i' }; + } + + + if (Number(query.status) === 1) { + + filter.StartDate = { $lte: now }; + filter.EndDate = { $gte: now }; + } else if (Number(query.status) === 0) { + filter.$or = [ + { StartDate: { $gt: now } }, + { EndDate: { $lt: now } }, + ]; + } + + const page = Number(query.page) || 1; + const limit = Number(query.limit) || 10; + const skip = (page - 1) * limit; + + console.log("query1111") + console.log(query) + + + const [discounts, total] = await Promise.all([ + this.discountModel + .find(filter) + .skip(skip) + .limit(limit) + .populate({ path: 'Images' }) + .populate({ path: 'Shop', select: 'ID Name' }) + .populate({ path: 'Type' }), + this.discountModel.countDocuments(filter), + ]); + + console.log("discounts") + console.log(discounts) + return { + message: 'لیست تخفیفات', + status: 200, + data: { + discounts, + total, + page, + lastPage: Math.ceil(total / limit), + }, + }; + } + + async findDiscountWithID(id: string): Promise { + + + const discount = await this.discountModel.findOne({ ID: id }).populate({ path: 'Images' }).populate({ path: 'Shop', select: 'Name ID' }).populate({ path: 'Type' }) + if (discount) { + return { + "message": "تخفیف موجود می باشد", + "status": 200, + "data": discount + } + + } else { + return { + "message": "تخفیف موجود نمی باشد", + "status": 401, + "data": null + } + } + + } + + async deleteDiscountWithID(id: string): Promise { + const discount = await this.discountModel.findOne({ ID: id }) + if (!discount) { + return { + message: 'تخفیف موجود نمی‌باشد', + status: 404, + data: null, + }; + } + + const deleteResult = await this.discountModel.deleteOne({ _id: discount._id }); + + if (deleteResult.deletedCount !== 1) { + return { + message: 'خطا در حذف تخفیف', + status: 500, + data: null, + }; + } + return { + message: 'تخفیف با موفقیت حذف شد', + status: 200, + data: null, + }; + } + + + async editDiscount( + id: string, + editDiscountDto: EditDiscountDto, + images: (string[] | null), + user: string, + ): Promise { + + const discount = await this.findDiscountWithID(id) + if (discount.status !== 200) { + return { + "data": "", + "message": "تخفیف با این شناسه ثبت نشده است", + "status": 404 + } + } + + const existCategory = await this.discountTypeService.findDiscountType(editDiscountDto.Type); + if (existCategory.status !== 200 || !existCategory.data) { + return { + message: 'نوع تخفیف ثبت نشده است', + status: 404, + data: null, + }; + } + const start = this.convertToMinutes(editDiscountDto.StartTime); + const end = this.convertToMinutes(editDiscountDto.EndTime); + if (end <= start) { + return { + message: 'تایم شروع نباید از تایم پایان بزرگتر باشد', + status: 404, + data: null, + }; + } + + var discountImages = discount.data.Images; + if (images && images.length > 0) { + var changeImages = discountImages.filter(item => !editDiscountDto.Image.includes(item)); + + images.forEach(item => { + if (!changeImages.includes(item)) { + changeImages.push(item); + } + }); + + } + + const updateDiscount = await this.discountModel.updateOne( + {_id:discount.data._id}, + {$set: { + "Name":editDiscountDto.Name, + "Images":changeImages, + "Type":editDiscountDto.Type, + "Description":editDiscountDto.Description, + "Price":editDiscountDto.Price, + "NPrice":editDiscountDto.NPrice, + "StartDate":editDiscountDto.Start, + "EndDate":editDiscountDto.End, + "StartTime":editDiscountDto.StartTime, + "EndTime":editDiscountDto.EndTime, + "Radius":editDiscountDto.Radius + }} + ) + if (updateDiscount.modifiedCount === 0) { + return { + message: 'آپدیت تخفیف با شکست مواجه شد', + data: null, + status: 500, + }; + } + + return { + message: 'تخفیف با موفقیت آپدیت شد', + data: null, + status: 200, + }; + + } + + } + + diff --git a/src/discount/dto/discount.dto.ts b/src/discount/dto/discount.dto.ts index 9e7a336..a9808aa 100644 --- a/src/discount/dto/discount.dto.ts +++ b/src/discount/dto/discount.dto.ts @@ -3,24 +3,26 @@ import { Type } from 'class-transformer'; export class CreateDiscountDto { - @IsNotEmpty({ message: 'شناسه فروشگاه نباید خالی باشد' }) - @IsString({ message: 'شناسه فروشگاه باید شامل متن باشد' }) - Shop: string; + @IsNotEmpty({ message: 'نام تخفیف نباید خالی باشد' }) + @IsString({ message: 'نام تخفیف باید شامل متن باشد' }) + Name: string; @IsNotEmpty({ message: 'نوع تخفیف نباید خالی باشد' }) @IsString({ message: 'نوع تخفیف باید شامل متن باشد' }) Type: string; - @IsNotEmpty({ message: 'شناسه محصول نباید خالی باشد' }) - @IsString({ message: 'شناسه محصول باید شامل متن باشد' }) - Product: string; - - @IsNotEmpty({ message: 'توضیحات تخفیف نباید خالی باشد' }) + @IsNotEmpty({ message: 'توضیحات تخفیف نباید خالی باشد' }) @IsString({ message: 'توضیحات تخفیف باید شامل متن باشد' }) @Length(0, 250, { message: 'طول توضیحات تخفیف حداکثر 250 حرف می تواند باشد' }) Description: string; + @IsNotEmpty({ message: 'قیمت محصول نباید خالی باشد' }) + @IsNumberString({},{ message: 'قیمت محصول باید شامل عدد باشد' }) + Price: number; + @IsNotEmpty({ message: 'قیمت محصول با احتساب تخفیف نباید خالی باشد' }) + @IsNumberString({},{ message: 'قیمت محصول با احتساب تخفیف باید شامل عدد باشد' }) + NPrice: number; @IsNotEmpty({ message: 'تاریخ شروع نباید خالی باشد' }) @IsDateString({},{ message: 'تاریخ شروع باید به فرمت تاریخ باشد' }) @@ -30,12 +32,11 @@ export class CreateDiscountDto { @IsDateString({},{ message: 'تاریخ پایان باید به فرمت تاریخ باشد' }) End: string; - @IsNotEmpty({ message: 'ساعت شروع نباید خالی باشد' }) @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { message: 'فرمت تایم شروع صحیح نیست', }) - StartTime: string; + StartTime: string; @IsNotEmpty({ message: 'ساعت پایان نباید خالی باشد' }) @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { @@ -43,7 +44,6 @@ export class CreateDiscountDto { }) EndTime: string; - @IsNotEmpty({ message: 'شعاع ارسال اعلان نباید خالی باشد' }) @IsNumberString({},{ message: 'شعاع ارسال اعلان باید شامل عدد باشد' }) Radius: number; diff --git a/src/discount/dto/discountfind.dto.ts b/src/discount/dto/discountfind.dto.ts new file mode 100644 index 0000000..31d4818 --- /dev/null +++ b/src/discount/dto/discountfind.dto.ts @@ -0,0 +1,21 @@ +// dto/pagination-query.dto.ts +import { IsOptional, IsEnum, IsString, IsNumberString, IsNumber } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class PaginationQueryDiscountDto { + @IsOptional() + @IsNumberString() + page: number; + + @IsOptional() + @IsNumberString() + limit: number; + + @IsOptional() + @IsString() + search: string; + + @IsOptional() + @IsNumberString() + status: number; +} diff --git a/src/discount/dto/editDiscount.dto.ts b/src/discount/dto/editDiscount.dto.ts new file mode 100644 index 0000000..9b77a6e --- /dev/null +++ b/src/discount/dto/editDiscount.dto.ts @@ -0,0 +1,58 @@ +import { IsNotEmpty,IsDateString, IsNumberString, IsString,ArrayMinSize, Matches,Length,ArrayNotEmpty, IsOptional,IsArray,ValidateNested,Validate, IsDate} from 'class-validator'; +import { Type } from 'class-transformer'; + +export class EditDiscountDto { + + @IsNotEmpty({ message: 'نام تخفیف نباید خالی باشد' }) + @IsString({ message: 'نام تخفیف باید شامل متن باشد' }) + Name: string; + + @IsNotEmpty({ message: 'نوع تخفیف نباید خالی باشد' }) + @IsString({ message: 'نوع تخفیف باید شامل متن باشد' }) + Type: string; + + @IsNotEmpty({ message: 'توضیحات تخفیف نباید خالی باشد' }) + @IsString({ message: 'توضیحات تخفیف باید شامل متن باشد' }) + @Length(0, 250, { message: 'طول توضیحات تخفیف حداکثر 250 حرف می تواند باشد' }) + Description: string; + + @IsNotEmpty({ message: 'قیمت محصول نباید خالی باشد' }) + @IsNumberString({},{ message: 'قیمت محصول باید شامل عدد باشد' }) + Price: number; + + @IsNotEmpty({ message: 'قیمت محصول با احتساب تخفیف نباید خالی باشد' }) + @IsNumberString({},{ message: 'قیمت محصول با احتساب تخفیف باید شامل عدد باشد' }) + NPrice: number; + + @IsArray({ message: 'تصاویر باید به صورت آرایه ارسال شوند' }) + @IsString({ each: true, message: 'هر تصویر باید به صورت رشته باشد' }) + Image: string[]; + + + @IsNotEmpty({ message: 'تاریخ شروع نباید خالی باشد' }) + @IsDateString({},{ message: 'تاریخ شروع باید به فرمت تاریخ باشد' }) + Start: string; + + @IsNotEmpty({ message: 'تاریخ پایان نباید خالی باشد' }) + @IsDateString({},{ message: 'تاریخ پایان باید به فرمت تاریخ باشد' }) + End: string; + + @IsNotEmpty({ message: 'ساعت شروع نباید خالی باشد' }) + @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { + message: 'فرمت تایم شروع صحیح نیست', + }) + StartTime: string; + + @IsNotEmpty({ message: 'ساعت پایان نباید خالی باشد' }) + @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { + message: 'فرمت تایم پایان صحیح نیست', + }) + EndTime: string; + + @IsNotEmpty({ message: 'شعاع ارسال اعلان نباید خالی باشد' }) + @IsNumberString({},{ message: 'شعاع ارسال اعلان باید شامل عدد باشد' }) + Radius: number; + + + +} \ No newline at end of file diff --git a/src/interfaces/request.ts b/src/interfaces/request.ts index 2631e9b..8ae2df2 100644 --- a/src/interfaces/request.ts +++ b/src/interfaces/request.ts @@ -3,6 +3,7 @@ import { Request } from "express"; export interface AuthenticatedRequest extends Request { user: { userId: string; + user_ID:string; }; } diff --git a/src/login/dto/sendcode.dto.ts b/src/login/dto/sendcode.dto.ts index 2be8675..18683eb 100644 --- a/src/login/dto/sendcode.dto.ts +++ b/src/login/dto/sendcode.dto.ts @@ -16,6 +16,4 @@ export class SendOTPCodeDto { @IsNumberString({},{ message: 'کد ارسالی باید فقط شامل اعداد باشد.' }) @Length(5, 5, { message: 'عدد باید دقیقاً ۵ رقم باشد' }) OTP: number; - - } diff --git a/src/login/login.controller.ts b/src/login/login.controller.ts index 611d157..d044523 100644 --- a/src/login/login.controller.ts +++ b/src/login/login.controller.ts @@ -8,7 +8,7 @@ export class LoginController { constructor(private readonly loginService:LoginService){} - @Post('/sendcode') + @Post('/sendcode') async sendCode(@Body() dto: CreateSellerDto,@Res() res: Response) { const result = await this.loginService.sendCode(dto); return res.status(result.status).json(result); diff --git a/src/login/login.module.ts b/src/login/login.module.ts index f876c42..b132782 100644 --- a/src/login/login.module.ts +++ b/src/login/login.module.ts @@ -4,8 +4,9 @@ import { LoginService } from './login.service'; import { SellerService } from 'src/seller/seller.service'; import { AuthModule } from 'src/auth/auth.module'; import { SellerModule } from 'src/seller/seller.module'; +import { SmsModule } from 'src/sms/sms.module'; @Module({ - imports:[SellerModule,AuthModule], + imports:[SellerModule,AuthModule,SmsModule], controllers: [LoginController], providers: [LoginService] }) diff --git a/src/login/login.service.ts b/src/login/login.service.ts index 7022ad3..1904c6d 100644 --- a/src/login/login.service.ts +++ b/src/login/login.service.ts @@ -5,6 +5,7 @@ import redis from '../redis/redis.provider'; import { SellerService } from 'src/seller/seller.service'; import {SendOTPCodeDto} from './dto/sendcode.dto' import { AuthService } from '../auth/auth.service' +import { SmsService } from 'src/sms/sms.service'; interface CreateSellerResponse { message: string; @@ -16,15 +17,19 @@ interface CreateSellerResponse { export class LoginService { - constructor(private readonly sellerService: SellerService,private readonly authService: AuthService) {} + constructor(private readonly sellerService: SellerService,private readonly authService: AuthService,private readonly smsService:SmsService) {} -async sendCode(createSellerDto: CreateSellerDto): Promise { +async sendCode(createSellerDto: CreateSellerDto): Promise { + const code = await generateCode(); console.log(`verified code is : ${code}`); const getSeller = await this.sellerService.findSellerwidtPhone(createSellerDto.Code,createSellerDto.Phone); if (!getSeller) { throw new NotFoundException('User not found'); } + const sendSms = await this.smsService.sendMessage(`0${createSellerDto.Phone}`, Number(code)); + console.log("sendSms") + console.log(sendSms) await redis.set(`login-code:${createSellerDto.Phone}`, Number(code), 'EX', 60); return { message: 'کد یکبار مصرف ارسال شد', @@ -55,9 +60,6 @@ async getcode(sendOTPCodeDto: SendOTPCodeDto): Promise { console.log("getSeller") console.log(getSeller) - if (!getSeller) { - throw new NotFoundException('User not found'); - } if(getSeller.status == 404){ const creatSeller = await this.sellerService.createWithPhone({Phone:sendOTPCodeDto.Phone,Code:sendOTPCodeDto.Code}) if(creatSeller.status == 200){ diff --git a/src/redis/redis.provider.ts b/src/redis/redis.provider.ts index 6e43a7a..e48fc2e 100644 --- a/src/redis/redis.provider.ts +++ b/src/redis/redis.provider.ts @@ -1,5 +1,5 @@ import Redis from 'ioredis'; -const redis = new Redis(process.env.REDIS_URL || 'redis://:eCwxwRr7YX9q0ynso4uR6HNf@fartakredis:6379/0'); +const redis = new Redis('redis://:eCwxwRr7YX9q0ynso4uR6HNf@fartakredis:6379/0'); export default redis; diff --git a/src/schemas/category.schema.ts b/src/schemas/category.schema.ts index 33f1480..7fc80e3 100644 --- a/src/schemas/category.schema.ts +++ b/src/schemas/category.schema.ts @@ -15,6 +15,9 @@ export class Category { @Prop({required:true,unique: true}) ID: string; + + @Prop() + Code:number; } export const CategorySchema = SchemaFactory.createForClass(Category); diff --git a/src/schemas/comment.schema.ts b/src/schemas/comment.schema.ts new file mode 100644 index 0000000..40d51b2 --- /dev/null +++ b/src/schemas/comment.schema.ts @@ -0,0 +1,31 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import mongoose, { HydratedDocument } from 'mongoose'; + + +export type CommentDocument = HydratedDocument; + +@Schema({ timestamps: true }) + +export class Comment { + @Prop({ type: { type: mongoose.Schema.Types.ObjectId, ref: 'Shop' } }) + Shops: mongoose.Schema.Types.ObjectId; + + @Prop({ type: { type: mongoose.Schema.Types.ObjectId, ref: 'Discount' } }) + Discount: mongoose.Schema.Types.ObjectId; + + + @Prop({ type: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }) + User: mongoose.Schema.Types.ObjectId; + + @Prop({ required: true }) + Text: string; + + @Prop({ default: null }) + Score: number; + @Prop({ required: true, unique: true }) + ID: string + +} + +export const commentSchema = SchemaFactory.createForClass(Comment); +commentSchema.index({ ID: 1 }); \ No newline at end of file diff --git a/src/schemas/discount.schema.ts b/src/schemas/discount.schema.ts index 9e3129c..5a94b40 100644 --- a/src/schemas/discount.schema.ts +++ b/src/schemas/discount.schema.ts @@ -7,18 +7,31 @@ export type DiscountDocument = HydratedDocument; @Schema({ timestamps: true }) export class Discount { + @Prop({required:true}) + Name: string; + @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Shop', default: null,required:true}) Shop: mongoose.Schema.Types.ObjectId; - @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Product', default: null,required:true }) - Product: mongoose.Schema.Types.ObjectId; + @Prop({ type: [mongoose.Schema.Types.ObjectId], ref: 'File', default: [] }) + Images: mongoose.Schema.Types.ObjectId[]; @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'DiscountType', default: null,required:true }) Type: mongoose.Schema.Types.ObjectId; + @Prop() + ProductDescription: string + @Prop({}) Description: string; + @Prop() + Price: number; + + @Prop() + NPrice: number; + + @Prop({required:true}) StartDate:Date diff --git a/src/schemas/discountType.schema.ts b/src/schemas/discountType.schema.ts index c0ccdbb..4472320 100644 --- a/src/schemas/discountType.schema.ts +++ b/src/schemas/discountType.schema.ts @@ -15,7 +15,9 @@ export class DiscountType { @Prop({required:true,unique: true}) ID: string; - + + @Prop() + Code:number; } export const DiscountTypeSchema = SchemaFactory.createForClass(DiscountType); diff --git a/src/schemas/file.schema.ts b/src/schemas/file.schema.ts index 8396267..70b02b5 100644 --- a/src/schemas/file.schema.ts +++ b/src/schemas/file.schema.ts @@ -7,8 +7,8 @@ export type FileDocument = HydratedDocument; @Schema({ timestamps: true }) export class File { - @Prop({ required: true,enum:['image','video','document']}) - FileType: string; + @Prop() +FileType: string; @Prop({required:false}) Title:string diff --git a/src/schemas/seller.schema.ts b/src/schemas/seller.schema.ts index 5a9bae8..b1bf4f9 100644 --- a/src/schemas/seller.schema.ts +++ b/src/schemas/seller.schema.ts @@ -6,7 +6,7 @@ export type SellerDocument = HydratedDocument; @Schema({ timestamps: true }) export class Seller { - @Prop({ unique: true }) + @Prop() Email: string; @Prop({ unique: true }) @@ -35,6 +35,9 @@ export class Seller { @Prop() CountryCode: number; + + @Prop({ default: null }) + TokenFireBase: string; } export const SellerSchema = SchemaFactory.createForClass(Seller); \ No newline at end of file diff --git a/src/schemas/shop.schema.ts b/src/schemas/shop.schema.ts index 3590fe1..289299b 100644 --- a/src/schemas/shop.schema.ts +++ b/src/schemas/shop.schema.ts @@ -19,6 +19,16 @@ class ShopSchedule { Status: boolean; } +@Schema() +class UserInfo { + @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }) + User: mongoose.Schema.Types.ObjectId; + + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'File' }], default: [] }) + Images: mongoose.Schema.Types.ObjectId[]; +} + + @Schema({ timestamps: true }) export class Shop { @Prop({required:true}) @@ -75,8 +85,11 @@ export class Shop { @Prop() ShopNumber: number; - @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'File' }], default: [] }) - Images: mongoose.Schema.Types.ObjectId[]; + @Prop() + Phone: number; + + @Prop({ type: [UserInfo], default: [] }) + Images: UserInfo[]; @Prop({ type: [ShopSchedule], default: [] }) Schedule: ShopSchedule[]; @@ -86,6 +99,10 @@ export class Shop { @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Order' }], default: [] }) Orders: mongoose.Schema.Types.ObjectId[]; + + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }], default: [] }) + Comments: mongoose.Schema.Types.ObjectId[]; + @Prop({required:true,unique:true}) ID:string diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts new file mode 100644 index 0000000..0177ec7 --- /dev/null +++ b/src/schemas/user.schema.ts @@ -0,0 +1,72 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import mongoose, { HydratedDocument } from 'mongoose'; + +export enum Gender { + Male = 'male', + Female = 'female', + None = 'none', +} + +export type UserDocument = HydratedDocument; + + +@Schema() +export class GpsPoint { + @Prop({ type: String, enum: ['Point'], required: true, default: 'Point' }) + type: 'Point'; + + @Prop({ type: [Number], required: true }) + coordinates: [number, number]; +} +export const GpsPointSchema = SchemaFactory.createForClass(GpsPoint); + +@Schema({ timestamps: true }) +export class User { + + @Prop() + Name: string; + + @Prop({ type: String, enum: Gender }) + Gender: Gender; + + @Prop() + Email: string; + + @Prop({ unique: true }) + ID: string; + + @Prop({ required: true, unique: true }) + Phone: number; + + @Prop({ required: false }) + verify: boolean; + + + @Prop({ default: null }) + LastLogin: Date; + + @Prop({ default: null }) + IpAddress: string; + + + @Prop({ type: [GpsPointSchema] }) + Gps: GpsPoint[]; + + @Prop({ type: GpsPointSchema }) + CurrentGps: GpsPoint; + + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }], default: [] }) + FCategory: mongoose.Schema.Types.ObjectId[]; + + + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }], default: [] }) + NotifCategory: mongoose.Schema.Types.ObjectId[]; + + @Prop() + CountryCode: number; + + @Prop({ default: null }) + TokenFireBase: string; +} + +export const UserSchema = SchemaFactory.createForClass(User); \ No newline at end of file diff --git a/src/seller/dto/token-firebase.dto.ts b/src/seller/dto/token-firebase.dto.ts new file mode 100644 index 0000000..809eb2a --- /dev/null +++ b/src/seller/dto/token-firebase.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator"; + +export class CreatTokenFireBase { + @IsNotEmpty({message:"توکن نباید خالی باشد. "}) + @IsString({message:"توکن باید متن باشد . "}) + Token:string; +} \ No newline at end of file diff --git a/src/seller/seller.controller.ts b/src/seller/seller.controller.ts index 22ae719..2998b2b 100644 --- a/src/seller/seller.controller.ts +++ b/src/seller/seller.controller.ts @@ -1,4 +1,35 @@ -import { Controller } from '@nestjs/common'; +import { Controller, + Post, + UploadedFile, + UseInterceptors, + Body, + UseGuards, + Res, + Req, + BadRequestException, + Get } from '@nestjs/common'; +import { SellerService } from './seller.service'; +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { AuthenticatedRequest } from 'src/interfaces/request'; +import { CreatTokenFireBase } from './dto/token-firebase.dto'; +import { Response } from 'express'; @Controller('seller') -export class SellerController {} +export class SellerController { + + constructor (private readonly sellerService:SellerService){} + + @UseGuards(JwtAuthGuard) + @Post('/firebaseUpdate') + async updateFireBaseToken ( + @Body() body:CreatTokenFireBase, + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ){ + const updateToken = await this.sellerService.updateTokenFireBase(body.Token,req.user.userId) + return res.status(updateToken.status).json(updateToken.data) + } + + + +} diff --git a/src/seller/seller.service.ts b/src/seller/seller.service.ts index 4a8c6b9..4349e49 100644 --- a/src/seller/seller.service.ts +++ b/src/seller/seller.service.ts @@ -30,7 +30,8 @@ export class SellerService { "LastLogin": null, "IpAddress": null, "Shops": [], - "CountryCode": 98 + "CountryCode": 98, + "TokenFireBase":"" }); const savedSeller = await createdSeller.save(); @@ -107,6 +108,35 @@ export class SellerService { } } +async updateTokenFireBase (token:string,user:string) : Promise { + + const seller = await this.findSellerwidtID(user) + if(!seller){ + return { + "data":null, + "status": 404, + "message": "فروشنده موجود نیست" + } + } +const updateSeller = await this.sellerModel.updateOne({_id:seller.data._id},{$set:{TokenFireBase:token}}) + if (updateSeller.modifiedCount === 0) { + return { + "data":null, + "status": 500, + "message": "آپدیت با شکست مواجه شد" + } + +} + + return { + data: null, + status: 200, + message: "به‌روزرسانی انجام شد" + } + +} + + diff --git a/src/shop/dto/creat-shop.dto.ts b/src/shop/dto/creat-shop.dto.ts index a81522d..b72bf84 100644 --- a/src/shop/dto/creat-shop.dto.ts +++ b/src/shop/dto/creat-shop.dto.ts @@ -17,7 +17,7 @@ export class CreateShopDto { //@Validate(IsCategoryExists) Category: string; - @IsNotEmpty({ message: 'نام استان نباید خالی باشد' }) + @IsNotEmpty({ message: 'نام استان نباید خالی باشد' }) @IsString({ message: 'نام استان باید شامل متن باشد' }) @Length(0, 20, { message: 'طول نام استان حداکثر 20 حرف می تواند باشد' }) Province: string; @@ -43,9 +43,14 @@ export class CreateShopDto { @IsOptional() - @IsNumberString({},{ message: 'شماره تلفن باید فقط شامل اعداد باشد.' }) - @Length(1, 12, { message: 'شماره تلفن باید حداکثر 12 رقم باشد.' }) - ShopNumber: string; + @IsNumberString({},{ message: 'تلفن فروشگاه باید فقط شامل اعداد باشد.' }) + @Length(1, 12, { message: 'تلفن فروشگاه باید حداکثر 12 رقم باشد.' }) + Phone: string; + + + @IsOptional() + @IsNumberString({},{ message: 'پلاک باید فقط شامل اعداد باشد.' }) + ShopNumber: number; @IsNotEmpty({message: 'کد پستی نباید خالی باشد.'}) @IsNumberString({},{ message: 'کد پستی باید فقط شامل اعداد باشد.' }) diff --git a/src/shop/shop.controller.ts b/src/shop/shop.controller.ts index 2c4ac1f..67c7a28 100644 --- a/src/shop/shop.controller.ts +++ b/src/shop/shop.controller.ts @@ -8,6 +8,7 @@ import { Res, Req, BadRequestException, + Get, } from '@nestjs/common'; import { Response } from 'express'; import { UploadService } from '../upload/upload.service'; @@ -31,11 +32,7 @@ export class ShopController { }, fileFilter: (req, file, cb) => { const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg']; - if (!allowedMimeTypes.includes(file.mimetype)) { - cb(new BadRequestException('فقط فرمت PNG یا JPG مجاز است'), false); - } else { - cb(null, true); - } + cb(null, true); }, }), ) @@ -58,6 +55,7 @@ export class ShopController { if (file) { const result = await this.upload.uploadFileSingle(file); + console.log(result) if (result.status !== 200) { return res.status(400).json({ message: 'آپلود لوگو با خطا مواجه شد', @@ -70,4 +68,14 @@ export class ShopController { const createdShop = await this.shopService.creatshop(shopData, fileId, req.user.userId); return res.status(createdShop.status).json(createdShop); } + @UseGuards(JwtAuthGuard) + @Get('/get') + async getDataShopSeller( + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ) { + const createdShop = await this.shopService.findShopWithSeller(req.user.userId); + return res.status(createdShop.status).json(createdShop); + } + } diff --git a/src/shop/shop.service.ts b/src/shop/shop.service.ts index c69b6e9..a30dda1 100644 --- a/src/shop/shop.service.ts +++ b/src/shop/shop.service.ts @@ -23,6 +23,14 @@ export class ShopService { "data":null } } + const existShop = await this.shopModel.findOne({Seller:user}).select('-_id') + if(existShop){ + return { + "message":"فروشگاهی قبلا ثبت شده است", + "status":409, + "data":existShop + } + } try { const newShop = new this.shopModel({ "Name":creatshopDto.Name, @@ -32,7 +40,7 @@ export class ShopService { "Address":creatshopDto.Address, "Map":{ "type":'Point', - "coordinates":[creatshopDto.Coordinates.latitude,creatshopDto.Coordinates.latitude] + "coordinates":[creatshopDto.Coordinates.latitude,creatshopDto.Coordinates.longitude] }, "Property":creatshopDto.Property, "BusinessLicense":creatshopDto.BusinessLicense, @@ -41,6 +49,7 @@ export class ShopService { "Province":creatshopDto.Province, "PostalCode":creatshopDto.PostalCode, "ShopNumber":creatshopDto.ShopNumber, + "Phone":creatshopDto.Phone, "Images":null, "Schedule":creatshopDto.Schedule, "Discounts":[], @@ -101,7 +110,27 @@ export class ShopService { async findShopSingle(id:string) : Promise{ - const shop = await this.shopModel.findOne({ID:id}) + const shop = await this.shopModel.findOne({ID:id}).select('-_id') + if(shop){ + return { + "message":"فروشگاه ثبت شده است", + "status":200, + "data":shop + } + }else { + return { + "message":"فروشگاهی با این شناسه موجود نیست", + "status":404, + "data":null + } + + } + + + } + async findShopWithSeller(id:string) : Promise{ + + const shop = await this.shopModel.findOne({Seller:id}).populate({path:'Seller',select:'-_id'}).populate({path:'Category',select:'-_id'}) if(shop){ return { "message":"فروشگاه ثبت شده است", diff --git a/src/sms/sms.controller.spec.ts b/src/sms/sms.controller.spec.ts new file mode 100644 index 0000000..ef21174 --- /dev/null +++ b/src/sms/sms.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SmsController } from './sms.controller'; + +describe('SmsController', () => { + let controller: SmsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SmsController], + }).compile(); + + controller = module.get(SmsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/sms/sms.controller.ts b/src/sms/sms.controller.ts new file mode 100644 index 0000000..8d8af5e --- /dev/null +++ b/src/sms/sms.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('sms') +export class SmsController {} diff --git a/src/sms/sms.module.ts b/src/sms/sms.module.ts new file mode 100644 index 0000000..39e2ee2 --- /dev/null +++ b/src/sms/sms.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { SmsController } from './sms.controller'; +import { SmsService } from './sms.service'; + +@Module({ + providers: [SmsService], + exports: [SmsService], +}) + +export class SmsModule {} diff --git a/src/sms/sms.service.spec.ts b/src/sms/sms.service.spec.ts new file mode 100644 index 0000000..4085265 --- /dev/null +++ b/src/sms/sms.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SmsService } from './sms.service'; + +describe('SmsService', () => { + let service: SmsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SmsService], + }).compile(); + + service = module.get(SmsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/sms/sms.service.ts b/src/sms/sms.service.ts new file mode 100644 index 0000000..cf2e80c --- /dev/null +++ b/src/sms/sms.service.ts @@ -0,0 +1,36 @@ +import { Injectable, Logger } from '@nestjs/common'; +import axios from 'axios'; + +@Injectable() +export class SmsService { + private readonly logger = new Logger(SmsService.name); + + private readonly API_KEY = '346446725341592F2F4554514846714A366A6349327A376671316779412F63474F48684D6348372B326A553D'; + private readonly TEMPLATE = 'verify'; + + /** + * ارسال پیامک تاییدیه با قالب کاوه‌نگار + * @param receptor شماره گیرنده (مثلاً: "09123456789") + * @param token1 مقدار توکن (مثلاً: 123456) + */ + async sendMessage(receptor: string, token1: number): Promise { + const token2 = receptor; + const url = `https://api.kavenegar.com/v1/${this.API_KEY}/verify/lookup.json`; + + try { + const response = await axios.post(url, null, { + params: { + receptor, + template: this.TEMPLATE, + token: token1, + token2, + }, + }); + + console.log(`پیام با موفقیت برای ${receptor} ارسال شد.`); + console.log(response.data); + } catch (error: any) { + console.error(`خطا در ارسال پیام برای ${receptor}:`, error?.response?.data || error.message); + } + } +} diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts index aeca13c..2f39055 100644 --- a/src/upload/upload.service.ts +++ b/src/upload/upload.service.ts @@ -61,8 +61,10 @@ const keyWithExt = `${key}${extension}`; ContentType: file.mimetype, }); - await this.s3.send(command); + const dataupload = await this.s3.send(command); const mimeType = file.mimetype; + console.log("dataupload") + console.log(dataupload) let fileType: 'image' | 'video' | 'document' | 'other' = 'other'; @@ -72,7 +74,7 @@ const keyWithExt = `${key}${extension}`; fileType = 'video'; } else if ( mimeType === 'application/pdf' || - mimeType === 'application/msword' || + mimeType === 'application/msword' || 'application/octet-stream' || mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ) { fileType = 'document'; @@ -80,6 +82,8 @@ const keyWithExt = `${key}${extension}`; try { + console.log("file") + console.log(file) const newFile = new this.fileModel({ "FileType":fileType, "Title":"avatar", @@ -88,6 +92,8 @@ try { "Url":urlFile, "ID":key }) + console.log("newFile") + console.log(newFile) const savedSeller = await newFile.save(); return { "message":"فایل با موفقیت آپلود شد .", diff --git a/src/user/dto/creat-seller.dto.ts b/src/user/dto/creat-seller.dto.ts new file mode 100644 index 0000000..0895738 --- /dev/null +++ b/src/user/dto/creat-seller.dto.ts @@ -0,0 +1,13 @@ +import { IsNotEmpty, IsNumberString, Length, Matches } from 'class-validator'; + +export class CreateUserDto { + @IsNotEmpty({ message: 'کد کشور نمی‌تواند خالی باشد.' }) + @IsNumberString({},{ message: 'کد کشور باید فقط شامل اعداد باشد.' }) + @Length(1, 4, { message: 'کد کشور باید بین 1 تا 4 رقم باشد.' }) + Code: number; + + @IsNotEmpty({ message: 'شماره تلفن نمی‌تواند خالی باشد.' }) + @IsNumberString({},{ message: 'شماره تلفن باید فقط شامل اعداد باشد.' }) + @Length(10, 10, { message: 'شماره تلفن باید دقیقاً 10 رقم باشد.' }) + Phone: number; +} diff --git a/src/user/dto/token-firebase.dto.ts b/src/user/dto/token-firebase.dto.ts new file mode 100644 index 0000000..809eb2a --- /dev/null +++ b/src/user/dto/token-firebase.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator"; + +export class CreatTokenFireBase { + @IsNotEmpty({message:"توکن نباید خالی باشد. "}) + @IsString({message:"توکن باید متن باشد . "}) + Token:string; +} \ No newline at end of file diff --git a/src/user/user.controller.spec.ts b/src/user/user.controller.spec.ts new file mode 100644 index 0000000..1d82215 --- /dev/null +++ b/src/user/user.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SellerController } from './user.controller'; + +describe('SellerController', () => { + let controller: SellerController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SellerController], + }).compile(); + + controller = module.get(SellerController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts new file mode 100644 index 0000000..c495b51 --- /dev/null +++ b/src/user/user.controller.ts @@ -0,0 +1,35 @@ +import { Controller, + Post, + UploadedFile, + UseInterceptors, + Body, + UseGuards, + Res, + Req, + BadRequestException, + Get } from '@nestjs/common'; +import { UserService } from './user.service'; +import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { AuthenticatedRequest } from 'src/interfaces/request'; +import { CreatTokenFireBase } from './dto/token-firebase.dto'; +import { Response } from 'express'; + +@Controller('seller') +export class UserController { + + constructor (private readonly userService:UserService){} + + @UseGuards(JwtAuthGuard) + @Post('/firebaseUpdate') + async updateFireBaseToken ( + @Body() body:CreatTokenFireBase, + @Req() req: AuthenticatedRequest, + @Res() res: Response, + ){ + const updateToken = await this.userService.updateTokenFireBase(body.Token,req.user.userId) + return res.status(updateToken.status).json(updateToken.data) + } + + + +} diff --git a/src/user/user.module.ts b/src/user/user.module.ts new file mode 100644 index 0000000..10239ff --- /dev/null +++ b/src/user/user.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { UserController } from './user.controller'; +import { UserService } from './user.service'; +import { MongooseModule } from '@nestjs/mongoose'; +import { UserSchema } from 'src/schemas/user.schema'; + +@Module({ + imports:[ + MongooseModule.forFeature([ + { name:'User', schema: UserSchema }, + ])], + controllers: [UserController], + providers: [UserService], + exports:[UserService] +}) +export class UserModule {} diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts new file mode 100644 index 0000000..651802b --- /dev/null +++ b/src/user/user.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SellerService } from './user.service'; + +describe('SellerService', () => { + let service: SellerService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SellerService], + }).compile(); + + service = module.get(SellerService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/user/user.service.ts b/src/user/user.service.ts new file mode 100644 index 0000000..35cd89b --- /dev/null +++ b/src/user/user.service.ts @@ -0,0 +1,140 @@ + +import { Injectable, NotFoundException, InternalServerErrorException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { User, UserDocument } from '../schemas/user.schema'; +import { CreateUserDto } from './dto/creat-seller.dto'; +import { v4 as uuidv4 } from 'uuid'; + +interface CreateSellerResponse { + message: string; + status: number; + data: any; +} + +@Injectable() +export class UserService { + constructor( + @InjectModel(User.name) private userModel: Model, + ) {} + async createWithPhone(createUserDto: CreateUserDto): Promise { + const newUuid: string = uuidv4(); + try { + const createdUser = new this.userModel({ + "Email": null, + "ID": newUuid, + "Phone": createUserDto.Phone, + "verify": false, + "LastLogin":new Date(), + "IpAddress": null, + "CountryCode": 98, + "TokenFireBase":"" + }); + const savedUser = await createdUser.save(); + + return { + "message": "کاربر با موفقیت ساخته شد", + "status": 200, + "data": { + "_id":createdUser._id, + "ID":createdUser.ID + } + }; + } catch (e) { + console.error("Error saving seller:", e); + throw new InternalServerErrorException("ذخیره دیتا با شکست مواجه شد"); + + } + } + async findUserwidtID(id: string): Promise { + try { + const user = await this.userModel.findOne({ ID: id }).exec(); + if (!user) { + return { + "message" : "کاربری ای با این شناسه ایدی موجود نیست", + "status":404, + "data":null + } + } + return { + "message":"اطلاعات کاربر", + "status":200, + "data":{ + "ID":user.ID, + "Phone":user.Phone + } + } + + } catch (e) { + console.error("Error finding user:", e); + return { + "message":"خطا در دریافت اطلاعات کاربر", + "status":502, + "data":null + } + } +} + + async findUserwidtPhone(code:number,phone:number): Promise { + try { + const user = await this.userModel.findOne({CountryCode: code,Phone:phone }).exec(); + if (!user) { + return { + "message" : "کاربری ای با این شناسه ایدی موجود نیست", + "status":404, + "data":null + } + } + return { + "message":"اطلاعات کاربر", + "status":200, + "data":{ + "ID":user.ID, + "Phone":user.Phone, + "_id":user._id + } + } + + } catch (e) { + console.error("Error finding seller:", e); + return { + "message":"خطا در دریافت اطلاعات کاربر", + "status":502, + "data":null + } + } +} + +async updateTokenFireBase (token:string,user:string) : Promise { + + const userfind = await this.findUserwidtID(user) + if(!userfind){ + return { + "data":null, + "status": 404, + "message": "کاربر موجود نیست" + } + } +const updateUser = await this.userModel.updateOne({_id:userfind.data._id},{$set:{TokenFireBase:token}}) + if (updateUser.modifiedCount === 0) { + return { + "data":null, + "status": 500, + "message": "آپدیت با شکست مواجه شد" + } + +} + + return { + data: null, + status: 200, + message: "به‌روزرسانی انجام شد" + } + +} + + + + + +} \ No newline at end of file