feat: chuẩn bị backend cho tính năng anonymous user upload ảnh không cần đăng nhập

This commit is contained in:
2026-06-19 08:46:59 +07:00
parent 6f2dd0fc01
commit ef11309399
6 changed files with 125 additions and 5 deletions
+3 -2
View File
@@ -5,8 +5,8 @@ export declare class JwtStrategy extends JwtStrategy_base {
constructor(prisma: PrismaService);
validate(payload: any): Promise<{
id: string;
email: string;
passwordHash: string;
email: string | null;
passwordHash: string | null;
name: string | null;
phone: string | null;
address: string | null;
@@ -14,6 +14,7 @@ export declare class JwtStrategy extends JwtStrategy_base {
createdAt: Date;
isAdmin: boolean;
isBlocked: boolean;
isAnonymous: boolean;
}>;
}
export {};
+48
View File
@@ -279,6 +279,47 @@ let AuthController = class AuthController {
this.emailService = emailService;
this.cacheManager = cacheManager;
}
async convertGuestToOfficial(body) {
const { guestId, email, password, name } = body;
if (!guestId || !email || !password) {
throw new common_1.BadRequestException('Vui lòng cung cấp đủ guestId, email và mật khẩu.');
}
const existingOfficialUser = await this.prisma.user.findFirst({
where: {
email: email,
isAnonymous: false,
},
});
if (existingOfficialUser) {
throw new common_1.BadRequestException('Email này đã được một tài khoản khác sử dụng.');
}
const guestUser = await this.prisma.user.findUnique({
where: { id: guestId },
});
if (!guestUser || !guestUser.isAnonymous) {
throw new common_1.NotFoundException('Không tìm thấy tài khoản khách hoặc tài khoản này đã được chuyển đổi.');
}
const passwordHash = await bcrypt.hash(password, 10);
const updatedUser = await this.prisma.user.update({
where: { id: guestId },
data: {
email: email,
passwordHash: passwordHash,
name: name || guestUser.name,
isAnonymous: false,
},
});
const payload = { email: updatedUser.email, sub: updatedUser.id };
return {
access_token: this.jwtService.sign(payload),
user: {
id: updatedUser.id,
email: updatedUser.email,
name: updatedUser.name,
isAdmin: updatedUser.isAdmin,
},
};
}
async getStatus() {
const userCount = await this.prisma.user.count();
console.log(`[Status Check] Users found: ${userCount}`);
@@ -346,6 +387,13 @@ let AuthController = class AuthController {
return user;
}
};
__decorate([
(0, common_1.Post)('convert-guest'),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AuthController.prototype, "convertGuestToOfficial", null);
__decorate([
(0, common_1.Get)('status'),
__metadata("design:type", Function),
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
-- DropForeignKey
ALTER TABLE "Photo" DROP CONSTRAINT "Photo_tourId_fkey";
-- AlterTable
ALTER TABLE "Photo" ADD COLUMN "originalUrl" TEXT,
ALTER COLUMN "tourId" DROP NOT NULL,
ALTER COLUMN "imageUrl" DROP NOT NULL;
-- AlterTable
ALTER TABLE "User" ADD COLUMN "isAnonymous" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "email" DROP NOT NULL,
ALTER COLUMN "passwordHash" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "Photo" ADD CONSTRAINT "Photo_tourId_fkey" FOREIGN KEY ("tourId") REFERENCES "Tour"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+3 -2
View File
@@ -57,8 +57,8 @@ enum PrivacyLevel {
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
email String? @unique
passwordHash String?
name String?
phone String?
address String?
@@ -66,6 +66,7 @@ model User {
createdAt DateTime @default(now())
isAdmin Boolean @default(false)
isBlocked Boolean @default(false)
isAnonymous Boolean @default(false)
createdTours Tour[] @relation("TourCreator")
tourParticipations TourParticipant[]
+55
View File
@@ -262,6 +262,61 @@ class AuthController {
@Inject(CACHE_MANAGER) private cacheManager: Cache
) {}
@Post('convert-guest')
async convertGuestToOfficial(@Body() body: any) {
const { guestId, email, password, name } = body;
if (!guestId || !email || !password) {
throw new BadRequestException('Vui lòng cung cấp đủ guestId, email và mật khẩu.');
}
// 1. Kiểm tra xem email đã tồn tại với một tài khoản chính thức khác chưa
const existingOfficialUser = await this.prisma.user.findFirst({
where: {
email: email,
isAnonymous: false,
},
});
if (existingOfficialUser) {
throw new BadRequestException('Email này đã được một tài khoản khác sử dụng.');
}
// 2. Tìm tài khoản khách
const guestUser = await this.prisma.user.findUnique({
where: { id: guestId },
});
if (!guestUser || !guestUser.isAnonymous) {
throw new NotFoundException('Không tìm thấy tài khoản khách hoặc tài khoản này đã được chuyển đổi.');
}
// 3. Mã hóa mật khẩu và cập nhật người dùng
const passwordHash = await bcrypt.hash(password, 10);
const updatedUser = await this.prisma.user.update({
where: { id: guestId },
data: {
email: email,
passwordHash: passwordHash,
name: name || guestUser.name, // Cập nhật tên mới nếu có, nếu không giữ lại tên ẩn danh cũ
isAnonymous: false, // Đánh dấu đây là tài khoản chính thức
},
});
// 4. Tạo và trả về token để người dùng đăng nhập ngay lập tức
const payload = { email: updatedUser.email, sub: updatedUser.id };
return {
access_token: this.jwtService.sign(payload),
user: {
id: updatedUser.id,
email: updatedUser.email,
name: updatedUser.name,
isAdmin: updatedUser.isAdmin,
},
};
}
@Get('status')
async getStatus() {
const userCount = await this.prisma.user.count();