From 6a26ea9b78e4797e5b3ae811be2e996a0de76b22 Mon Sep 17 00:00:00 2001 From: vahidrezvani Date: Tue, 8 Jul 2025 10:10:52 +0330 Subject: [PATCH] =?UTF-8?q?=D8=A7=D9=BE=D9=84=D9=88=D8=AF=20=D8=B1=D9=88?= =?UTF-8?q?=DB=8C=20=D9=84=DB=8C=D8=A7=D8=B1=D8=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 40 +++++++++++++++++ package-lock.json | 51 +++++++++++++++++----- package.json | 1 + src/app.module.ts | 2 +- src/auth/auth.controller.ts | 35 ++++++++++++++- src/auth/google.strategy.ts | 24 +++++----- src/discount-type/discount-type.service.ts | 1 - src/redis/redis.provider.ts | 6 +-- 8 files changed, 131 insertions(+), 29 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38d928d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# ---- Base Stage ---- +FROM node:20-slim AS base + +# ---- Build Stage ---- +FROM base AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ + +# Install ALL dependencies (dev and prod) +# This is cached and only re-runs if the lockfile changes. +RUN npm ci + +# Copy the rest of the source code +COPY . . + +# Build the application +RUN npm run build + +# ---- Production Stage ---- +# Create the final, lean production image by copying only what's needed. +# This stage is very fast as it only contains COPY commands. +FROM base AS production +WORKDIR /app + +# Copy the pruned production dependencies, the built app, and package.json +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist +COPY --from=build /app/package.json ./package.json + +# Expose the port the app runs on +EXPOSE 3000 + +# Run the application as a non-root user for better security +USER node + +# Command to run the application +CMD ["node", "dist/main"] + diff --git a/package-lock.json b/package-lock.json index 48a90d1..ec49305 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", + "@types/passport-google-oauth20": "^2.0.16", "aws-sdk": "^2.1692.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", @@ -5228,7 +5229,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -5239,7 +5239,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -5285,7 +5284,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -5297,7 +5295,6 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -5327,7 +5324,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -5395,7 +5391,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/multer": { @@ -5417,25 +5412,62 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/oauth": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.16.tgz", + "integrity": "sha512-ayXK2CJ7uVieqhYOc6k/pIr5pcQxOLB6kBev+QUGS7oEZeTgIs1odDobXRqgfBPvXzl0wXCQHftV5220czZCPA==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "node_modules/@types/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -5446,7 +5478,6 @@ "version": "1.15.8", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", diff --git a/package.json b/package.json index b97fe23..02bae61 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@nestjs/mongoose": "^11.0.3", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", + "@types/passport-google-oauth20": "^2.0.16", "aws-sdk": "^2.1692.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", diff --git a/src/app.module.ts b/src/app.module.ts index c602bf7..4b450a9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -18,7 +18,7 @@ import { DiscountTypeModule } from './discount-type/discount-type.module'; @Module({ imports: [ CategoryModule,UploadModule,LoginModule,ShopModule,ProductModule,DiscountModule, - MongooseModule.forRoot('mongodb://localhost/fartak'), + MongooseModule.forRoot('mongodb://root:FcpaOAxvZd2IiqK5Vl5QRMTE@fartakdatabase:27017/my-app?authSource=admin'), ConfigModule.forRoot({ isGlobal: true }), DiscountTypeModule ], controllers: [AppController], diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index b87527a..8fddd7c 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,6 +1,37 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { JwtService } from '@nestjs/jwt'; +import { Response, Request } from 'express'; @Controller('auth') export class AuthController { - + constructor(private jwtService: JwtService) {} + + @Get('google') + @UseGuards(AuthGuard('google')) + async googleAuth() { + // Redirects to Google + } + + @Get('google/redirect') + @UseGuards(AuthGuard('google')) + async googleRedirect(@Req() req: Request, @Res() res: Response) { + const user = req.user as any; + + // You can store user in DB here if needed + + const payload = { + email: user.email, + name: `${user.firstName} ${user.lastName}`, + picture: user.picture, + }; + + const token = this.jwtService.sign(payload); + + // ✅ Option 1: Send token as JSON + // res.json({ token }); + + // ✅ Option 2: Redirect to frontend with token + res.redirect(`http://localhost:4200/login/success?token=${token}`); + } } diff --git a/src/auth/google.strategy.ts b/src/auth/google.strategy.ts index 3d92fdb..893fce5 100644 --- a/src/auth/google.strategy.ts +++ b/src/auth/google.strategy.ts @@ -1,22 +1,26 @@ +// auth/google.strategy.ts import { PassportStrategy } from '@nestjs/passport'; -import { Strategy, VerifyCallback } from 'passport-google-oauth20'; +import { Strategy, VerifyCallback, StrategyOptions } from 'passport-google-oauth20'; import { Injectable } from '@nestjs/common'; -import * as dotenv from 'dotenv'; - -dotenv.config(); +import { ConfigService } from '@nestjs/config'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { - constructor() { + constructor(private configService: ConfigService) { super({ - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: 'http://localhost:3000/auth/google/redirect', + clientID: configService.get('GOOGLE_CLIENT_ID'), + clientSecret: configService.get('GOOGLE_CLIENT_SECRET'), + callbackURL: configService.get('GOOGLE_CALLBACK_URL'), scope: ['email', 'profile'], - }); + } as StrategyOptions); // Fixes the typing error } - async validate(accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise { + async validate( + accessToken: string, + refreshToken: string, + profile: any, + done: VerifyCallback, + ): Promise { const { name, emails, photos } = profile; const user = { diff --git a/src/discount-type/discount-type.service.ts b/src/discount-type/discount-type.service.ts index 2efcb4f..d9e4f73 100644 --- a/src/discount-type/discount-type.service.ts +++ b/src/discount-type/discount-type.service.ts @@ -41,7 +41,6 @@ export class DiscountTypeService { } - async findDiscountType(id:string) :Promise { const category = await this.discountTypeModel.findOne({ID:id}) diff --git a/src/redis/redis.provider.ts b/src/redis/redis.provider.ts index 9a4320a..6e43a7a 100644 --- a/src/redis/redis.provider.ts +++ b/src/redis/redis.provider.ts @@ -1,9 +1,5 @@ -// src/redis.client.ts import Redis from 'ioredis'; -const redis = new Redis({ - host: 'localhost', // یا IP سرور Redis - port: 6379, // پورت پیش‌فرض Redis -}); +const redis = new Redis(process.env.REDIS_URL || 'redis://:eCwxwRr7YX9q0ynso4uR6HNf@fartakredis:6379/0'); export default redis;