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
|
||||
PORT=3002
|
||||
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_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?
|
||||
galleryUrls String[] @default([])
|
||||
galleryKeys String[] @default([])
|
||||
tags String[] @default([])
|
||||
published Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
variants ProductVariant[]
|
||||
orderItems OrderItem[]
|
||||
tags String[] @default([])
|
||||
published Boolean @default(true)
|
||||
vipEnabled Boolean @default(false)
|
||||
vipOnly Boolean @default(false)
|
||||
vipSpecialPrice Boolean @default(false)
|
||||
vipPrice Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
variants ProductVariant[]
|
||||
orderItems OrderItem[]
|
||||
|
||||
@@index([storeId])
|
||||
@@index([categoryId])
|
||||
@@index([brandId])
|
||||
@@index([price])
|
||||
@@index([published])
|
||||
@@index([vipEnabled])
|
||||
@@index([vipOnly])
|
||||
}
|
||||
|
||||
model ProductVariant {
|
||||
@@ -208,6 +214,7 @@ model ProductVariant {
|
||||
packageId String @default("")
|
||||
price Int @default(0)
|
||||
discountPrice Int?
|
||||
vipPrice Int?
|
||||
|
||||
@@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 { AuthService } from './auth.service';
|
||||
import { CurrentUser } from './decorators/current-user.decorator';
|
||||
import { CellNumberDto } from './dto/cell-number.dto';
|
||||
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { OtpVerifyDto } from './dto/otp-verify.dto';
|
||||
@@ -70,4 +71,13 @@ export class AuthController {
|
||||
me(@CurrentUser() user: AuthUser) {
|
||||
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 { AuthUser, displayName } from './auth.types';
|
||||
import { CellNumberDto } from './dto/cell-number.dto';
|
||||
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||
import { ForgotPasswordResetDto } from './dto/forgot-password-reset.dto';
|
||||
import { LoginDto } from './dto/login.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. */
|
||||
createAndSendOtpPublic(input: {
|
||||
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()
|
||||
@Min(0)
|
||||
discountPrice?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
vipPrice?: number;
|
||||
}
|
||||
|
||||
export class CreateProductDto {
|
||||
@@ -98,6 +103,23 @@ export class CreateProductDto {
|
||||
@IsBoolean()
|
||||
published?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipEnabled?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipOnly?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipSpecialPrice?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
vipPrice?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@@ -173,6 +195,23 @@ export class UpdateProductDto {
|
||||
@IsBoolean()
|
||||
published?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipEnabled?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipOnly?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
vipSpecialPrice?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
vipPrice?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -97,6 +97,10 @@ export class ProductsService {
|
||||
galleryKeys: dto.galleryKeys ?? [],
|
||||
tags: dto.tags ?? [],
|
||||
published: dto.published ?? true,
|
||||
vipEnabled: dto.vipEnabled ?? false,
|
||||
vipOnly: dto.vipOnly ?? false,
|
||||
vipSpecialPrice: dto.vipSpecialPrice ?? false,
|
||||
vipPrice: dto.vipPrice,
|
||||
variants: dto.variants?.length
|
||||
? {
|
||||
create: dto.variants.map((v) => ({
|
||||
@@ -105,6 +109,7 @@ export class ProductsService {
|
||||
packageId: v.packageId ?? '',
|
||||
price: v.price,
|
||||
discountPrice: v.discountPrice,
|
||||
vipPrice: v.vipPrice,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
@@ -132,6 +137,7 @@ export class ProductsService {
|
||||
packageId: v.packageId ?? '',
|
||||
price: v.price,
|
||||
discountPrice: v.discountPrice,
|
||||
vipPrice: v.vipPrice,
|
||||
})),
|
||||
});
|
||||
}
|
||||
@@ -164,6 +170,14 @@ export class ProductsService {
|
||||
: {}),
|
||||
...(dto.tags !== undefined ? { tags: dto.tags } : {}),
|
||||
...(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 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user