Add change-password endpoint and VIP product pricing fields.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
fe78a99b78
commit
3da8a5d4a2
+1
-1
@@ -10,7 +10,7 @@ DATABASE_URL=postgresql://mana:mana_secret@localhost:5436/mana_collection
|
|||||||
# API
|
# API
|
||||||
PORT=3002
|
PORT=3002
|
||||||
PUBLIC_SITE_ORIGIN=http://mana-collection.com:3000
|
PUBLIC_SITE_ORIGIN=http://mana-collection.com:3000
|
||||||
CORS_ORIGIN=http://localhost:3000,http://127.0.0.1:3000,http://mana-collection.com:3000,http://www.mana-collection.com:3000,http://store.mana-collection.com:3000,http://customer.mana-collection.com:3000,http://admin.mana-collection.com:3000,https://manacollections.ir,https://store.manacollections.ir,https://customer.manacollections.ir,https://admin.manacollections.ir
|
CORS_ORIGIN=http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,http://mana-collection.com:3000,http://www.mana-collection.com:3000,http://store.mana-collection.com:3001,http://customer.mana-collection.com:3001,http://admin.mana-collection.com:3001,https://manacollections.ir,https://store.manacollections.ir,https://customer.manacollections.ir,https://admin.manacollections.ir
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
JWT_ACCESS_SECRET=change-me-mana-access-secret-min-32-chars
|
JWT_ACCESS_SECRET=change-me-mana-access-secret-min-32-chars
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Product" ADD COLUMN "vipOnly" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Product_vipOnly_idx" ON "Product"("vipOnly");
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Product" ADD COLUMN "vipEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
ALTER TABLE "Product" ADD COLUMN "vipSpecialPrice" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
ALTER TABLE "Product" ADD COLUMN "vipPrice" INTEGER;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Product_vipEnabled_idx" ON "Product"("vipEnabled");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ProductVariant" ADD COLUMN "vipPrice" INTEGER;
|
||||||
+13
-6
@@ -185,18 +185,24 @@ model Product {
|
|||||||
imageKey String?
|
imageKey String?
|
||||||
galleryUrls String[] @default([])
|
galleryUrls String[] @default([])
|
||||||
galleryKeys String[] @default([])
|
galleryKeys String[] @default([])
|
||||||
tags String[] @default([])
|
tags String[] @default([])
|
||||||
published Boolean @default(true)
|
published Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
vipEnabled Boolean @default(false)
|
||||||
updatedAt DateTime @updatedAt
|
vipOnly Boolean @default(false)
|
||||||
variants ProductVariant[]
|
vipSpecialPrice Boolean @default(false)
|
||||||
orderItems OrderItem[]
|
vipPrice Int?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
variants ProductVariant[]
|
||||||
|
orderItems OrderItem[]
|
||||||
|
|
||||||
@@index([storeId])
|
@@index([storeId])
|
||||||
@@index([categoryId])
|
@@index([categoryId])
|
||||||
@@index([brandId])
|
@@index([brandId])
|
||||||
@@index([price])
|
@@index([price])
|
||||||
@@index([published])
|
@@index([published])
|
||||||
|
@@index([vipEnabled])
|
||||||
|
@@index([vipOnly])
|
||||||
}
|
}
|
||||||
|
|
||||||
model ProductVariant {
|
model ProductVariant {
|
||||||
@@ -208,6 +214,7 @@ model ProductVariant {
|
|||||||
packageId String @default("")
|
packageId String @default("")
|
||||||
price Int @default(0)
|
price Int @default(0)
|
||||||
discountPrice Int?
|
discountPrice Int?
|
||||||
|
vipPrice Int?
|
||||||
|
|
||||||
@@index([productId])
|
@@index([productId])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
import { Body, Controller, Get, Patch, Post, UseGuards } from '@nestjs/common';
|
||||||
import type { AuthUser } from './auth.types';
|
import type { AuthUser } from './auth.types';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { CurrentUser } from './decorators/current-user.decorator';
|
import { CurrentUser } from './decorators/current-user.decorator';
|
||||||
import { CellNumberDto } from './dto/cell-number.dto';
|
import { CellNumberDto } from './dto/cell-number.dto';
|
||||||
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||||
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
import { OtpVerifyDto } from './dto/otp-verify.dto';
|
import { OtpVerifyDto } from './dto/otp-verify.dto';
|
||||||
@@ -70,4 +71,13 @@ export class AuthController {
|
|||||||
me(@CurrentUser() user: AuthUser) {
|
me(@CurrentUser() user: AuthUser) {
|
||||||
return this.auth.me(user);
|
return this.auth.me(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Patch('password')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
changePassword(
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
@Body() dto: ChangePasswordDto,
|
||||||
|
) {
|
||||||
|
return this.auth.changePassword(user, dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
import { AuthUser, displayName } from './auth.types';
|
import { AuthUser, displayName } from './auth.types';
|
||||||
import { CellNumberDto } from './dto/cell-number.dto';
|
import { CellNumberDto } from './dto/cell-number.dto';
|
||||||
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||||
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
import { OtpVerifyDto } from './dto/otp-verify.dto';
|
import { OtpVerifyDto } from './dto/otp-verify.dto';
|
||||||
@@ -276,6 +277,15 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changePassword(actor: AuthUser, dto: ChangePasswordDto) {
|
||||||
|
const passwordHash = await bcrypt.hash(dto.password, 10);
|
||||||
|
await this.prisma.user.update({
|
||||||
|
where: { id: actor.id },
|
||||||
|
data: { passwordHash },
|
||||||
|
});
|
||||||
|
return { ok: true as const };
|
||||||
|
}
|
||||||
|
|
||||||
/** Expose OTP helpers for store registration flow. */
|
/** Expose OTP helpers for store registration flow. */
|
||||||
createAndSendOtpPublic(input: {
|
createAndSendOtpPublic(input: {
|
||||||
cellNumber: string;
|
cellNumber: string;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class ChangePasswordDto {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(4, { message: 'رمز عبور باید حداقل ۴ کاراکتر باشد' })
|
||||||
|
password!: string;
|
||||||
|
}
|
||||||
@@ -35,6 +35,11 @@ export class ProductVariantDto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
discountPrice?: number;
|
discountPrice?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
vipPrice?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateProductDto {
|
export class CreateProductDto {
|
||||||
@@ -98,6 +103,23 @@ export class CreateProductDto {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
published?: boolean;
|
published?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipEnabled?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipOnly?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipSpecialPrice?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
vipPrice?: number;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@@ -173,6 +195,23 @@ export class UpdateProductDto {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
published?: boolean;
|
published?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipEnabled?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipOnly?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
vipSpecialPrice?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
vipPrice?: number | null;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ export class ProductsService {
|
|||||||
galleryKeys: dto.galleryKeys ?? [],
|
galleryKeys: dto.galleryKeys ?? [],
|
||||||
tags: dto.tags ?? [],
|
tags: dto.tags ?? [],
|
||||||
published: dto.published ?? true,
|
published: dto.published ?? true,
|
||||||
|
vipEnabled: dto.vipEnabled ?? false,
|
||||||
|
vipOnly: dto.vipOnly ?? false,
|
||||||
|
vipSpecialPrice: dto.vipSpecialPrice ?? false,
|
||||||
|
vipPrice: dto.vipPrice,
|
||||||
variants: dto.variants?.length
|
variants: dto.variants?.length
|
||||||
? {
|
? {
|
||||||
create: dto.variants.map((v) => ({
|
create: dto.variants.map((v) => ({
|
||||||
@@ -105,6 +109,7 @@ export class ProductsService {
|
|||||||
packageId: v.packageId ?? '',
|
packageId: v.packageId ?? '',
|
||||||
price: v.price,
|
price: v.price,
|
||||||
discountPrice: v.discountPrice,
|
discountPrice: v.discountPrice,
|
||||||
|
vipPrice: v.vipPrice,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -132,6 +137,7 @@ export class ProductsService {
|
|||||||
packageId: v.packageId ?? '',
|
packageId: v.packageId ?? '',
|
||||||
price: v.price,
|
price: v.price,
|
||||||
discountPrice: v.discountPrice,
|
discountPrice: v.discountPrice,
|
||||||
|
vipPrice: v.vipPrice,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -164,6 +170,14 @@ export class ProductsService {
|
|||||||
: {}),
|
: {}),
|
||||||
...(dto.tags !== undefined ? { tags: dto.tags } : {}),
|
...(dto.tags !== undefined ? { tags: dto.tags } : {}),
|
||||||
...(dto.published !== undefined ? { published: dto.published } : {}),
|
...(dto.published !== undefined ? { published: dto.published } : {}),
|
||||||
|
...(dto.vipEnabled !== undefined
|
||||||
|
? { vipEnabled: dto.vipEnabled }
|
||||||
|
: {}),
|
||||||
|
...(dto.vipOnly !== undefined ? { vipOnly: dto.vipOnly } : {}),
|
||||||
|
...(dto.vipSpecialPrice !== undefined
|
||||||
|
? { vipSpecialPrice: dto.vipSpecialPrice }
|
||||||
|
: {}),
|
||||||
|
...(dto.vipPrice !== undefined ? { vipPrice: dto.vipPrice } : {}),
|
||||||
},
|
},
|
||||||
include: { variants: true },
|
include: { variants: true },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user