تغییرات جدید

This commit is contained in:
vahidrezvani 2025-07-19 11:58:27 +03:30
parent 6a26ea9b78
commit cb0cb9106e
52 changed files with 1228 additions and 113 deletions

52
package-lock.json generated
View File

@ -18,8 +18,10 @@
"@nestjs/mongoose": "^11.0.3", "@nestjs/mongoose": "^11.0.3",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@types/kavenegar": "^1.1.3",
"@types/passport-google-oauth20": "^2.0.16", "@types/passport-google-oauth20": "^2.0.16",
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"axios": "^1.10.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"dotenv": "^17.0.0", "dotenv": "^17.0.0",
@ -5380,6 +5382,12 @@
"@types/node": "*" "@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": { "node_modules/@types/methods": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@ -6838,7 +6846,6 @@
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
@ -6913,6 +6920,17 @@
"uuid": "dist/bin/uuid" "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": { "node_modules/b4a": {
"version": "1.6.7", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
@ -7818,7 +7836,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
@ -8162,7 +8179,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
@ -8419,7 +8435,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -9173,6 +9188,26 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@ -9237,7 +9272,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
@ -9264,7 +9298,6 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@ -9274,7 +9307,6 @@
"version": "2.1.35", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
@ -12432,6 +12464,12 @@
"node": ">= 0.10" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -29,8 +29,10 @@
"@nestjs/mongoose": "^11.0.3", "@nestjs/mongoose": "^11.0.3",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@types/kavenegar": "^1.1.3",
"@types/passport-google-oauth20": "^2.0.16", "@types/passport-google-oauth20": "^2.0.16",
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"axios": "^1.10.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"dotenv": "^17.0.0", "dotenv": "^17.0.0",

View File

@ -13,13 +13,16 @@ import { ConfigModule } from '@nestjs/config';
import { ProductModule } from './product/product.module'; import { ProductModule } from './product/product.module';
import { DiscountModule } from './discount/discount.module'; import { DiscountModule } from './discount/discount.module';
import { DiscountTypeModule } from './discount-type/discount-type.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({ @Module({
imports: [ imports: [
CategoryModule,UploadModule,LoginModule,ShopModule,ProductModule,DiscountModule, CategoryModule,UploadModule,LoginModule,ShopModule,ProductModule,DiscountModule,
MongooseModule.forRoot('mongodb://root:FcpaOAxvZd2IiqK5Vl5QRMTE@fartakdatabase:27017/my-app?authSource=admin'), 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], controllers: [AppController],
providers: [AppService], providers: [AppService],

View File

@ -2,10 +2,12 @@ import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { Response, Request } from 'express'; import { Response, Request } from 'express';
import { JwtAuthGuard } from './jwt-auth.guard';
import { AuthenticatedRequest } from 'src/interfaces/request';
import { AuthService } from './auth.service';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
constructor(private jwtService: JwtService) {} constructor(private jwtService: JwtService,private readonly authService:AuthService) {}
@Get('google') @Get('google')
@UseGuards(AuthGuard('google')) @UseGuards(AuthGuard('google'))
@ -34,4 +36,17 @@ export class AuthController {
// ✅ Option 2: Redirect to frontend with token // ✅ Option 2: Redirect to frontend with token
res.redirect(`http://localhost:4200/login/success?token=${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);
}
} }

View File

@ -5,11 +5,13 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy'; import { JwtStrategy } from './jwt.strategy';
import { AuthController } from './auth.controller'; import { AuthController } from './auth.controller';
import { SellerModule } from 'src/seller/seller.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule, ConfigModule,
PassportModule, PassportModule,
SellerModule,
JwtModule.registerAsync({ JwtModule.registerAsync({
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],

View File

@ -1,21 +1,23 @@
// auth.service.ts // auth.service.ts
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { CreateSellerResponse } from 'src/interfaces/request';
import { SellerService } from 'src/seller/seller.service';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
constructor(private jwtService: JwtService) {} constructor(private jwtService: JwtService,private readonly sellerService:SellerService) {}
async generateTokens(payload:any) { async generateTokens(payload:any) {
console.log(process.env.JWT_SECRET) console.log(process.env.JWT_SECRET)
const accessToken = await this.jwtService.signAsync(payload, { const accessToken = await this.jwtService.signAsync(payload, {
secret: process.env.JWT_SECRET, secret: process.env.JWT_SECRET,
expiresIn: '15m', expiresIn: '1d',
}); });
const refreshToken = await this.jwtService.signAsync(payload, { const refreshToken = await this.jwtService.signAsync(payload, {
secret: process.env.JWT_REFRESH_SECRET, secret: process.env.JWT_REFRESH_SECRET,
expiresIn: '7d', expiresIn: '30d',
}); });
return { return {
@ -24,9 +26,9 @@ export class AuthService {
}; };
} }
async verifyRefreshToken(token: string) { async verifyToken(token: string) {
return this.jwtService.verifyAsync(token, { return this.jwtService.verifyAsync(token, {
secret: process.env.JWT_REFRESH_SECRET, secret: process.env.JWT_SECRET,
}); });
} }
async loginWithGoogle(user: any) { async loginWithGoogle(user: any) {
@ -37,5 +39,25 @@ export class AuthService {
}; };
} }
async verifyauth(user: string):Promise <CreateSellerResponse>{
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
}
}
}
} }

View File

@ -1,21 +1,27 @@
import { Injectable } from '@nestjs/common'; import { Injectable,UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { UserService } from 'src/user/user.service';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) { constructor(private configService: ConfigService,private readonly userService:UserService) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false, ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_REFRESH_SECRET'), secretOrKey: configService.get<string>('JWT_SECRET'),
}); });
} }
async validate(payload: any) { async validate(payload: any) {
console.log("payload") 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};
} }
} }

View File

@ -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 { Response } from 'express';
import { CategoryService } from './category.service'; import { CategoryService } from './category.service';
import { CreatCategoryDto } from './dto/creatCategory.dto'; import { CreatCategoryDto } from './dto/creatCategory.dto';
@ -12,4 +12,10 @@ export class CategoryController {
const result = await this.categoryService.creatCategory(dto); const result = await this.categoryService.creatCategory(dto);
return res.status(result.status).json(result); 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);
}
} }

View File

@ -24,7 +24,8 @@ export class CategoryService {
const newCategory = new this.categoryModel({ const newCategory = new this.categoryModel({
Name:creatCategory.Name, Name:creatCategory.Name,
Description:creatCategory.Description, Description:creatCategory.Description,
ID:newUuid ID:newUuid,
Code:creatCategory.Code,
}) })
const result = newCategory.save() const result = newCategory.save()
@ -63,6 +64,14 @@ export class CategoryService {
} }
} }
} }
async findAllCategory() :Promise <CreateSellerResponse> {
const categories = await this.categoryModel.find().select('-_id')
return {
"message":"همه دسته بندی ها",
"status":200,
"data":categories
}
}
} }

View File

@ -1,4 +1,4 @@
import { IsNotEmpty, IsString } from "class-validator"; import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator";
export class CreatCategoryDto { export class CreatCategoryDto {
@IsNotEmpty({message:"نام دسته بندی نباید خالی باشد . "}) @IsNotEmpty({message:"نام دسته بندی نباید خالی باشد . "})
@ -9,6 +9,13 @@ export class CreatCategoryDto {
@IsString({message:"نام دسته بندی باید متن باشد . "}) @IsString({message:"نام دسته بندی باید متن باشد . "})
Description:string Description:string
@IsNotEmpty({ message: 'کد دسته بندی نمی‌تواند خالی باشد.' })
@IsNumberString({},{ message: 'کد دسته بندی باید فقط شامل اعداد باشد.' })
Code: number;
} }

View File

@ -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 { Response } from 'express';
import { DiscountTypeService } from './discount-type.service'; import { DiscountTypeService } from './discount-type.service';
import { CreatDiscountTypeDto } from './dto/discountType.dto'; import { CreatDiscountTypeDto } from './dto/discountType.dto';
@ -12,4 +12,10 @@ export class DiscountTypeController {
const result = await this.discountTypeService.creatDiscountType(dto); const result = await this.discountTypeService.creatDiscountType(dto);
return res.status(result.status).json(result); 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);
}
} }

View File

@ -20,7 +20,8 @@ export class DiscountTypeService {
const newDiscountType = new this.discountTypeModel({ const newDiscountType = new this.discountTypeModel({
Name:creatDiscountType.Name, Name:creatDiscountType.Name,
Description:creatDiscountType.Description, Description:creatDiscountType.Description,
ID:newUuid ID:newUuid,
Code:creatDiscountType.Code
}) })
const result = newDiscountType.save() const result = newDiscountType.save()
@ -59,5 +60,15 @@ export class DiscountTypeService {
} }
} }
async findAllDiscountType() :Promise <CreateSellerResponse> {
const categories = await this.discountTypeModel.find().select('-_id')
return {
"message":"همه دسته بندی های تخفیف",
"status":200,
"data":categories
}
}
} }

View File

@ -1,14 +1,19 @@
import { IsNotEmpty, IsString } from "class-validator"; import { IsNotEmpty, IsString,IsNumberString } from "class-validator";
export class CreatDiscountTypeDto { export class CreatDiscountTypeDto {
@IsNotEmpty({message:ام دسته بندی نباید خالی باشد . "}) @IsNotEmpty({message:وع تخفیف نباید خالی باشد . "})
@IsString({message:ام دسته بندی باید متن باشد . "}) @IsString({message:وع تخفیف باید متن باشد . "})
Name:string; Name:string;
@IsNotEmpty({message:ام دسته بندی نباید خالی باشد . "}) @IsNotEmpty({message:وع تخفیف نباید خالی باشد . "})
@IsString({message:ام دسته بندی باید متن باشد . "}) @IsString({message:وع تخفیف باید متن باشد . "})
Description:string Description:string
@IsNotEmpty({ message: 'نوع تخفیف نمی‌تواند خالی باشد.' })
@IsNumberString({},{ message: 'نوع تخفیف باید فقط شامل اعداد باشد.' })
Code: number;
} }

View File

@ -5,28 +5,139 @@ import {
UseGuards, UseGuards,
Res, Res,
Req, Req,
UploadedFiles,
BadRequestException,
UseInterceptors,
Param,
Get,
Query
} from '@nestjs/common'; } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { FilesInterceptor } from '@nestjs/platform-express';
import { DiscountService } from './discount.service'; import { DiscountService } from './discount.service';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { AuthenticatedRequest } from 'src/interfaces/request'; import { AuthenticatedRequest } from 'src/interfaces/request';
import { CreateDiscountDto } from './dto/discount.dto'; 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'; import { Response } from 'express';
@Controller('discount') @Controller('discount')
export class DiscountController { export class DiscountController {
constructor( constructor(
private readonly upload: UploadService,
private readonly discountService: DiscountService, private readonly discountService: DiscountService,
) {} ) {}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Post('/add') @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( async create(
@Body() dto:CreateDiscountDto, @Body() dto:CreateDiscountDto,
@UploadedFiles() files: Express.Multer.File[],
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
@Res() res: Response, @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); 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);
}
} }

View File

@ -6,11 +6,13 @@ import { DiscountSchema } from 'src/schemas/discount.schema';
import { ProductModule } from 'src/product/product.module'; import { ProductModule } from 'src/product/product.module';
import { ShopModule } from 'src/shop/shop.module'; import { ShopModule } from 'src/shop/shop.module';
import { DiscountTypeModule } from 'src/discount-type/discount-type.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({ @Module({
imports:[ imports:[
MongooseModule.forFeature([ MongooseModule.forFeature([
{ name: 'Discount', schema: DiscountSchema }, { name: 'Discount', schema: DiscountSchema },
]),ProductModule,DiscountTypeModule,ShopModule ]),UploadModule,DiscountTypeModule,ShopModule
], ],

View File

@ -9,26 +9,32 @@ import { CreateDiscountDto } from './dto/discount.dto';
import { CreateSellerResponse } from 'src/interfaces/request'; import { CreateSellerResponse } from 'src/interfaces/request';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { ShopService } from 'src/shop/shop.service'; 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() @Injectable()
export class DiscountService { export class DiscountService {
constructor( constructor(
@InjectModel(Discount.name) @InjectModel(Discount.name)
private readonly discountModel: Model<DiscountDocument>, private readonly discountModel: Model<DiscountDocument>,
private readonly shopService: ShopService, private readonly shopService: ShopService,
private readonly productService: ProductService,
private readonly discountTypeService: DiscountTypeService, 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( async creatDiscount(
creatDiscountDto: CreateDiscountDto, creatDiscountDto: CreateDiscountDto,
images: (string[] | null),
user: any, user: any,
): Promise<CreateSellerResponse> { ): Promise<CreateSellerResponse> {
const newUuid: string = uuidv4(); const newUuid: string = uuidv4();
console.log("creatDiscountDto") console.log("creatDiscountDto")
console.log(creatDiscountDto) console.log(creatDiscountDto)
const existCategory = await this.discountTypeService.findDiscountType(creatDiscountDto.Type); const existCategory = await this.discountTypeService.findDiscountType(creatDiscountDto.Type);
if (existCategory.status !== 200 || !existCategory.data) { if (existCategory.status !== 200 || !existCategory.data) {
return { 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) { if (existShop.status !== 200 || !existShop.data) {
return { return {
message: 'فروشگاه ثبت نشده است', message: 'فروشگاه ثبت نشده است',
@ -46,50 +52,43 @@ export class DiscountService {
data: null, data: null,
}; };
} }
if (!existShop.data.Seller) {
return {
message: 'اطلاعات فروشنده فروشگاه ناقص است',
status: 500,
data: null,
};
}
const sellerId = new Types.ObjectId(user); const start = this.convertToMinutes(creatDiscountDto.StartTime);
const shopSellerId = existShop.data.Seller; const end = this.convertToMinutes(creatDiscountDto.EndTime);
if (end <= start) {
if (!sellerId.equals(shopSellerId)) {
return { return {
message: 'این فروشنده مجاز به ثبت تخفیف برای این فروشگاه نیست', message: 'تایم شروع نباید از تایم پایان بزرگتر باشد',
status: 403,
data: null,
};
}
const existProduct = await this.productService.findProductSingle(creatDiscountDto.Product);
if (existProduct.status !== 200 || !existProduct.data) {
return {
message: 'محصول ثبت نشده است',
status: 404, status: 404,
data: null, 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 { return {
message: 'این محصول در این فروشگاه ثبت نشده است', message: 'این فروشنده مجاز به ثبت تخفیف برای این فروشگاه نیست',
status: 400, status: 403,
data: null, data: null,
}; };
} }
*/
try { try {
const newDiscount = new this.discountModel({ const newDiscount = new this.discountModel({
Name: creatDiscountDto.Name,
Shop: existShop.data._id, Shop: existShop.data._id,
Product: existProduct.data._id, Images: images,
Type: existCategory.data._id, Type: existCategory.data._id,
ProductDescription: "محصول جدید",
Description: creatDiscountDto.Description, Description: creatDiscountDto.Description,
Price: creatDiscountDto.Price,
NPrice: creatDiscountDto.NPrice,
StartDate: creatDiscountDto.Start, StartDate: creatDiscountDto.Start,
EndDate: creatDiscountDto.End, EndDate: creatDiscountDto.End,
StartTime: creatDiscountDto.StartTime, StartTime: creatDiscountDto.StartTime,
@ -116,4 +115,204 @@ export class DiscountService {
}; };
} }
} }
async findAlldiscount(
user: any,
query: PaginationQueryDiscountDto,
): Promise<CreateSellerResponse> {
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<CreateSellerResponse> {
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<CreateSellerResponse> {
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<CreateSellerResponse> {
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,
};
}
} }

View File

@ -3,24 +3,26 @@ import { Type } from 'class-transformer';
export class CreateDiscountDto { export class CreateDiscountDto {
@IsNotEmpty({ message: 'شناسه فروشگاه نباید خالی باشد' }) @IsNotEmpty({ message: 'نام تخفیف نباید خالی باشد' })
@IsString({ message: 'شناسه فروشگاه باید شامل متن باشد' }) @IsString({ message: 'نام تخفیف باید شامل متن باشد' })
Shop: string; Name: string;
@IsNotEmpty({ message: 'نوع تخفیف نباید خالی باشد' }) @IsNotEmpty({ message: 'نوع تخفیف نباید خالی باشد' })
@IsString({ message: 'نوع تخفیف باید شامل متن باشد' }) @IsString({ message: 'نوع تخفیف باید شامل متن باشد' })
Type: string; Type: string;
@IsNotEmpty({ message: 'شناسه محصول نباید خالی باشد' }) @IsNotEmpty({ message: 'توضیحات تخفیف نباید خالی باشد' })
@IsString({ message: 'شناسه محصول باید شامل متن باشد' })
Product: string;
@IsNotEmpty({ message: 'توضیحات تخفیف نباید خالی باشد' })
@IsString({ message: 'توضیحات تخفیف باید شامل متن باشد' }) @IsString({ message: 'توضیحات تخفیف باید شامل متن باشد' })
@Length(0, 250, { message: 'طول توضیحات تخفیف حداکثر 250 حرف می تواند باشد' }) @Length(0, 250, { message: 'طول توضیحات تخفیف حداکثر 250 حرف می تواند باشد' })
Description: string; Description: string;
@IsNotEmpty({ message: 'قیمت محصول نباید خالی باشد' })
@IsNumberString({},{ message: 'قیمت محصول باید شامل عدد باشد' })
Price: number;
@IsNotEmpty({ message: 'قیمت محصول با احتساب تخفیف نباید خالی باشد' })
@IsNumberString({},{ message: 'قیمت محصول با احتساب تخفیف باید شامل عدد باشد' })
NPrice: number;
@IsNotEmpty({ message: 'تاریخ شروع نباید خالی باشد' }) @IsNotEmpty({ message: 'تاریخ شروع نباید خالی باشد' })
@IsDateString({},{ message: 'تاریخ شروع باید به فرمت تاریخ باشد' }) @IsDateString({},{ message: 'تاریخ شروع باید به فرمت تاریخ باشد' })
@ -30,12 +32,11 @@ export class CreateDiscountDto {
@IsDateString({},{ message: 'تاریخ پایان باید به فرمت تاریخ باشد' }) @IsDateString({},{ message: 'تاریخ پایان باید به فرمت تاریخ باشد' })
End: string; End: string;
@IsNotEmpty({ message: 'ساعت شروع نباید خالی باشد' }) @IsNotEmpty({ message: 'ساعت شروع نباید خالی باشد' })
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, {
message: 'فرمت تایم شروع صحیح نیست', message: 'فرمت تایم شروع صحیح نیست',
}) })
StartTime: string; StartTime: string;
@IsNotEmpty({ message: 'ساعت پایان نباید خالی باشد' }) @IsNotEmpty({ message: 'ساعت پایان نباید خالی باشد' })
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, { @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, {
@ -43,7 +44,6 @@ export class CreateDiscountDto {
}) })
EndTime: string; EndTime: string;
@IsNotEmpty({ message: 'شعاع ارسال اعلان نباید خالی باشد' }) @IsNotEmpty({ message: 'شعاع ارسال اعلان نباید خالی باشد' })
@IsNumberString({},{ message: 'شعاع ارسال اعلان باید شامل عدد باشد' }) @IsNumberString({},{ message: 'شعاع ارسال اعلان باید شامل عدد باشد' })
Radius: number; Radius: number;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -3,6 +3,7 @@ import { Request } from "express";
export interface AuthenticatedRequest extends Request { export interface AuthenticatedRequest extends Request {
user: { user: {
userId: string; userId: string;
user_ID:string;
}; };
} }

View File

@ -16,6 +16,4 @@ export class SendOTPCodeDto {
@IsNumberString({},{ message: 'کد ارسالی باید فقط شامل اعداد باشد.' }) @IsNumberString({},{ message: 'کد ارسالی باید فقط شامل اعداد باشد.' })
@Length(5, 5, { message: 'عدد باید دقیقاً ۵ رقم باشد' }) @Length(5, 5, { message: 'عدد باید دقیقاً ۵ رقم باشد' })
OTP: number; OTP: number;
} }

View File

@ -8,7 +8,7 @@ export class LoginController {
constructor(private readonly loginService:LoginService){} constructor(private readonly loginService:LoginService){}
@Post('/sendcode') @Post('/sendcode')
async sendCode(@Body() dto: CreateSellerDto,@Res() res: Response) { async sendCode(@Body() dto: CreateSellerDto,@Res() res: Response) {
const result = await this.loginService.sendCode(dto); const result = await this.loginService.sendCode(dto);
return res.status(result.status).json(result); return res.status(result.status).json(result);

View File

@ -4,8 +4,9 @@ import { LoginService } from './login.service';
import { SellerService } from 'src/seller/seller.service'; import { SellerService } from 'src/seller/seller.service';
import { AuthModule } from 'src/auth/auth.module'; import { AuthModule } from 'src/auth/auth.module';
import { SellerModule } from 'src/seller/seller.module'; import { SellerModule } from 'src/seller/seller.module';
import { SmsModule } from 'src/sms/sms.module';
@Module({ @Module({
imports:[SellerModule,AuthModule], imports:[SellerModule,AuthModule,SmsModule],
controllers: [LoginController], controllers: [LoginController],
providers: [LoginService] providers: [LoginService]
}) })

View File

@ -5,6 +5,7 @@ import redis from '../redis/redis.provider';
import { SellerService } from 'src/seller/seller.service'; import { SellerService } from 'src/seller/seller.service';
import {SendOTPCodeDto} from './dto/sendcode.dto' import {SendOTPCodeDto} from './dto/sendcode.dto'
import { AuthService } from '../auth/auth.service' import { AuthService } from '../auth/auth.service'
import { SmsService } from 'src/sms/sms.service';
interface CreateSellerResponse { interface CreateSellerResponse {
message: string; message: string;
@ -16,15 +17,19 @@ interface CreateSellerResponse {
export class LoginService { 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<CreateSellerResponse> { async sendCode(createSellerDto: CreateSellerDto): Promise<CreateSellerResponse> {
const code = await generateCode(); const code = await generateCode();
console.log(`verified code is : ${code}`); console.log(`verified code is : ${code}`);
const getSeller = await this.sellerService.findSellerwidtPhone(createSellerDto.Code,createSellerDto.Phone); const getSeller = await this.sellerService.findSellerwidtPhone(createSellerDto.Code,createSellerDto.Phone);
if (!getSeller) { if (!getSeller) {
throw new NotFoundException('User not found'); 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); await redis.set(`login-code:${createSellerDto.Phone}`, Number(code), 'EX', 60);
return { return {
message: 'کد یکبار مصرف ارسال شد', message: 'کد یکبار مصرف ارسال شد',
@ -55,9 +60,6 @@ async getcode(sendOTPCodeDto: SendOTPCodeDto): Promise<CreateSellerResponse> {
console.log("getSeller") console.log("getSeller")
console.log(getSeller) console.log(getSeller)
if (!getSeller) {
throw new NotFoundException('User not found');
}
if(getSeller.status == 404){ if(getSeller.status == 404){
const creatSeller = await this.sellerService.createWithPhone({Phone:sendOTPCodeDto.Phone,Code:sendOTPCodeDto.Code}) const creatSeller = await this.sellerService.createWithPhone({Phone:sendOTPCodeDto.Phone,Code:sendOTPCodeDto.Code})
if(creatSeller.status == 200){ if(creatSeller.status == 200){

View File

@ -1,5 +1,5 @@
import Redis from 'ioredis'; 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; export default redis;

View File

@ -15,6 +15,9 @@ export class Category {
@Prop({required:true,unique: true}) @Prop({required:true,unique: true})
ID: string; ID: string;
@Prop()
Code:number;
} }
export const CategorySchema = SchemaFactory.createForClass(Category); export const CategorySchema = SchemaFactory.createForClass(Category);

View File

@ -0,0 +1,31 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { HydratedDocument } from 'mongoose';
export type CommentDocument = HydratedDocument<Comment>;
@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 });

View File

@ -7,18 +7,31 @@ export type DiscountDocument = HydratedDocument<Discount>;
@Schema({ timestamps: true }) @Schema({ timestamps: true })
export class Discount { export class Discount {
@Prop({required:true})
Name: string;
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Shop', default: null,required:true}) @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Shop', default: null,required:true})
Shop: mongoose.Schema.Types.ObjectId; Shop: mongoose.Schema.Types.ObjectId;
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Product', default: null,required:true }) @Prop({ type: [mongoose.Schema.Types.ObjectId], ref: 'File', default: [] })
Product: mongoose.Schema.Types.ObjectId; Images: mongoose.Schema.Types.ObjectId[];
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'DiscountType', default: null,required:true }) @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'DiscountType', default: null,required:true })
Type: mongoose.Schema.Types.ObjectId; Type: mongoose.Schema.Types.ObjectId;
@Prop()
ProductDescription: string
@Prop({}) @Prop({})
Description: string; Description: string;
@Prop()
Price: number;
@Prop()
NPrice: number;
@Prop({required:true}) @Prop({required:true})
StartDate:Date StartDate:Date

View File

@ -15,7 +15,9 @@ export class DiscountType {
@Prop({required:true,unique: true}) @Prop({required:true,unique: true})
ID: string; ID: string;
@Prop()
Code:number;
} }
export const DiscountTypeSchema = SchemaFactory.createForClass(DiscountType); export const DiscountTypeSchema = SchemaFactory.createForClass(DiscountType);

View File

@ -7,8 +7,8 @@ export type FileDocument = HydratedDocument<File>;
@Schema({ timestamps: true }) @Schema({ timestamps: true })
export class File { export class File {
@Prop({ required: true,enum:['image','video','document']}) @Prop()
FileType: string; FileType: string;
@Prop({required:false}) @Prop({required:false})
Title:string Title:string

View File

@ -6,7 +6,7 @@ export type SellerDocument = HydratedDocument<Seller>;
@Schema({ timestamps: true }) @Schema({ timestamps: true })
export class Seller { export class Seller {
@Prop({ unique: true }) @Prop()
Email: string; Email: string;
@Prop({ unique: true }) @Prop({ unique: true })
@ -35,6 +35,9 @@ export class Seller {
@Prop() @Prop()
CountryCode: number; CountryCode: number;
@Prop({ default: null })
TokenFireBase: string;
} }
export const SellerSchema = SchemaFactory.createForClass(Seller); export const SellerSchema = SchemaFactory.createForClass(Seller);

View File

@ -19,6 +19,16 @@ class ShopSchedule {
Status: boolean; 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 }) @Schema({ timestamps: true })
export class Shop { export class Shop {
@Prop({required:true}) @Prop({required:true})
@ -75,8 +85,11 @@ export class Shop {
@Prop() @Prop()
ShopNumber: number; ShopNumber: number;
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'File' }], default: [] }) @Prop()
Images: mongoose.Schema.Types.ObjectId[]; Phone: number;
@Prop({ type: [UserInfo], default: [] })
Images: UserInfo[];
@Prop({ type: [ShopSchedule], default: [] }) @Prop({ type: [ShopSchedule], default: [] })
Schedule: ShopSchedule[]; Schedule: ShopSchedule[];
@ -86,6 +99,10 @@ export class Shop {
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Order' }], default: [] }) @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Order' }], default: [] })
Orders: mongoose.Schema.Types.ObjectId[]; 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}) @Prop({required:true,unique:true})
ID:string ID:string

View File

@ -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<User>;
@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);

View File

@ -0,0 +1,7 @@
import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator";
export class CreatTokenFireBase {
@IsNotEmpty({message:"توکن نباید خالی باشد. "})
@IsString({message:"توکن باید متن باشد . "})
Token:string;
}

View File

@ -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') @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)
}
}

View File

@ -30,7 +30,8 @@ export class SellerService {
"LastLogin": null, "LastLogin": null,
"IpAddress": null, "IpAddress": null,
"Shops": [], "Shops": [],
"CountryCode": 98 "CountryCode": 98,
"TokenFireBase":""
}); });
const savedSeller = await createdSeller.save(); const savedSeller = await createdSeller.save();
@ -107,6 +108,35 @@ export class SellerService {
} }
} }
async updateTokenFireBase (token:string,user:string) : Promise <CreateSellerResponse> {
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: "به‌روزرسانی انجام شد"
}
}

View File

@ -17,7 +17,7 @@ export class CreateShopDto {
//@Validate(IsCategoryExists) //@Validate(IsCategoryExists)
Category: string; Category: string;
@IsNotEmpty({ message: 'نام استان نباید خالی باشد' }) @IsNotEmpty({ message: 'نام استان نباید خالی باشد' })
@IsString({ message: 'نام استان باید شامل متن باشد' }) @IsString({ message: 'نام استان باید شامل متن باشد' })
@Length(0, 20, { message: 'طول نام استان حداکثر 20 حرف می تواند باشد' }) @Length(0, 20, { message: 'طول نام استان حداکثر 20 حرف می تواند باشد' })
Province: string; Province: string;
@ -43,9 +43,14 @@ export class CreateShopDto {
@IsOptional() @IsOptional()
@IsNumberString({},{ message: 'شماره تلفن باید فقط شامل اعداد باشد.' }) @IsNumberString({},{ message: 'تلفن فروشگاه باید فقط شامل اعداد باشد.' })
@Length(1, 12, { message: 'شماره تلفن باید حداکثر 12 رقم باشد.' }) @Length(1, 12, { message: 'تلفن فروشگاه باید حداکثر 12 رقم باشد.' })
ShopNumber: string; Phone: string;
@IsOptional()
@IsNumberString({},{ message: 'پلاک باید فقط شامل اعداد باشد.' })
ShopNumber: number;
@IsNotEmpty({message: 'کد پستی نباید خالی باشد.'}) @IsNotEmpty({message: 'کد پستی نباید خالی باشد.'})
@IsNumberString({},{ message: 'کد پستی باید فقط شامل اعداد باشد.' }) @IsNumberString({},{ message: 'کد پستی باید فقط شامل اعداد باشد.' })

View File

@ -8,6 +8,7 @@ import {
Res, Res,
Req, Req,
BadRequestException, BadRequestException,
Get,
} from '@nestjs/common'; } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
import { UploadService } from '../upload/upload.service'; import { UploadService } from '../upload/upload.service';
@ -31,11 +32,7 @@ export class ShopController {
}, },
fileFilter: (req, file, cb) => { fileFilter: (req, file, cb) => {
const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg']; const allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg'];
if (!allowedMimeTypes.includes(file.mimetype)) { cb(null, true);
cb(new BadRequestException('فقط فرمت PNG یا JPG مجاز است'), false);
} else {
cb(null, true);
}
}, },
}), }),
) )
@ -58,6 +55,7 @@ export class ShopController {
if (file) { if (file) {
const result = await this.upload.uploadFileSingle(file); const result = await this.upload.uploadFileSingle(file);
console.log(result)
if (result.status !== 200) { if (result.status !== 200) {
return res.status(400).json({ return res.status(400).json({
message: 'آپلود لوگو با خطا مواجه شد', message: 'آپلود لوگو با خطا مواجه شد',
@ -70,4 +68,14 @@ export class ShopController {
const createdShop = await this.shopService.creatshop(shopData, fileId, req.user.userId); const createdShop = await this.shopService.creatshop(shopData, fileId, req.user.userId);
return res.status(createdShop.status).json(createdShop); 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);
}
} }

View File

@ -23,6 +23,14 @@ export class ShopService {
"data":null "data":null
} }
} }
const existShop = await this.shopModel.findOne({Seller:user}).select('-_id')
if(existShop){
return {
"message":"فروشگاهی قبلا ثبت شده است",
"status":409,
"data":existShop
}
}
try { try {
const newShop = new this.shopModel({ const newShop = new this.shopModel({
"Name":creatshopDto.Name, "Name":creatshopDto.Name,
@ -32,7 +40,7 @@ export class ShopService {
"Address":creatshopDto.Address, "Address":creatshopDto.Address,
"Map":{ "Map":{
"type":'Point', "type":'Point',
"coordinates":[creatshopDto.Coordinates.latitude,creatshopDto.Coordinates.latitude] "coordinates":[creatshopDto.Coordinates.latitude,creatshopDto.Coordinates.longitude]
}, },
"Property":creatshopDto.Property, "Property":creatshopDto.Property,
"BusinessLicense":creatshopDto.BusinessLicense, "BusinessLicense":creatshopDto.BusinessLicense,
@ -41,6 +49,7 @@ export class ShopService {
"Province":creatshopDto.Province, "Province":creatshopDto.Province,
"PostalCode":creatshopDto.PostalCode, "PostalCode":creatshopDto.PostalCode,
"ShopNumber":creatshopDto.ShopNumber, "ShopNumber":creatshopDto.ShopNumber,
"Phone":creatshopDto.Phone,
"Images":null, "Images":null,
"Schedule":creatshopDto.Schedule, "Schedule":creatshopDto.Schedule,
"Discounts":[], "Discounts":[],
@ -101,7 +110,27 @@ export class ShopService {
async findShopSingle(id:string) : Promise<CreateSellerResponse>{ async findShopSingle(id:string) : Promise<CreateSellerResponse>{
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<CreateSellerResponse>{
const shop = await this.shopModel.findOne({Seller:id}).populate({path:'Seller',select:'-_id'}).populate({path:'Category',select:'-_id'})
if(shop){ if(shop){
return { return {
"message":"فروشگاه ثبت شده است", "message":"فروشگاه ثبت شده است",

View File

@ -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>(SmsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';
@Controller('sms')
export class SmsController {}

10
src/sms/sms.module.ts Normal file
View File

@ -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 {}

View File

@ -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>(SmsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

36
src/sms/sms.service.ts Normal file
View File

@ -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<void> {
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);
}
}
}

View File

@ -61,8 +61,10 @@ const keyWithExt = `${key}${extension}`;
ContentType: file.mimetype, ContentType: file.mimetype,
}); });
await this.s3.send(command); const dataupload = await this.s3.send(command);
const mimeType = file.mimetype; const mimeType = file.mimetype;
console.log("dataupload")
console.log(dataupload)
let fileType: 'image' | 'video' | 'document' | 'other' = 'other'; let fileType: 'image' | 'video' | 'document' | 'other' = 'other';
@ -72,7 +74,7 @@ const keyWithExt = `${key}${extension}`;
fileType = 'video'; fileType = 'video';
} else if ( } else if (
mimeType === 'application/pdf' || mimeType === 'application/pdf' ||
mimeType === 'application/msword' || mimeType === 'application/msword' || 'application/octet-stream' ||
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
) { ) {
fileType = 'document'; fileType = 'document';
@ -80,6 +82,8 @@ const keyWithExt = `${key}${extension}`;
try { try {
console.log("file")
console.log(file)
const newFile = new this.fileModel({ const newFile = new this.fileModel({
"FileType":fileType, "FileType":fileType,
"Title":"avatar", "Title":"avatar",
@ -88,6 +92,8 @@ try {
"Url":urlFile, "Url":urlFile,
"ID":key "ID":key
}) })
console.log("newFile")
console.log(newFile)
const savedSeller = await newFile.save(); const savedSeller = await newFile.save();
return { return {
"message":"فایل با موفقیت آپلود شد .", "message":"فایل با موفقیت آپلود شد .",

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
import { IsNotEmpty, IsString,IsNumberString,Length} from "class-validator";
export class CreatTokenFireBase {
@IsNotEmpty({message:"توکن نباید خالی باشد. "})
@IsString({message:"توکن باید متن باشد . "})
Token:string;
}

View File

@ -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>(SellerController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -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)
}
}

16
src/user/user.module.ts Normal file
View File

@ -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 {}

View File

@ -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>(SellerService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

140
src/user/user.service.ts Normal file
View File

@ -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<UserDocument>,
) {}
async createWithPhone(createUserDto: CreateUserDto): Promise<CreateSellerResponse> {
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<CreateSellerResponse> {
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<CreateSellerResponse> {
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 <CreateSellerResponse> {
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: "به‌روزرسانی انجام شد"
}
}
}