feat: remove OTP gate from transactions, fix categories auth, add implementation plan

- Remove OtpGateGuard from transactions controller (OTP verified at login)
- Fix categories controller to use authenticated user instead of TEMP_USER_ID
- Add comprehensive implementation plan document
- Update .env.example with WEB_APP_URL
- Prepare for admin dashboard development
This commit is contained in:
dwindown
2025-10-11 14:00:11 +07:00
parent 0da6071eb3
commit 249f3a9d7d
159 changed files with 13748 additions and 3369 deletions

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { Injectable, BadRequestException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { getTempUserId } from '../common/user.util';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
@@ -10,4 +11,101 @@ export class UsersService {
const userId = getTempUserId();
return this.prisma.user.findUnique({ where: { id: userId } });
}
}
async updateProfile(userId: string, data: { name?: string; phone?: string }) {
try {
const user = await this.prisma.user.update({
where: { id: userId },
data: {
...(data.name !== undefined && { name: data.name }),
...(data.phone !== undefined && { phone: data.phone }),
},
select: {
id: true,
email: true,
name: true,
phone: true,
avatarUrl: true,
},
});
return {
success: true,
message: 'Profile updated successfully',
user,
};
} catch (error: any) {
if (error.code === 'P2002') {
throw new BadRequestException('Phone number already in use');
}
throw error;
}
}
async getAuthInfo(userId: string) {
// Get user with password hash and avatar
const user = await this.prisma.user.findUnique({
where: { id: userId },
select: {
passwordHash: true,
avatarUrl: true,
},
});
// Check if user has Google OAuth (avatar from Google or starts with /avatars/)
const hasGoogleAuth =
user?.avatarUrl?.includes('googleusercontent.com') ||
user?.avatarUrl?.startsWith('/avatars/') ||
false;
return {
hasGoogleAuth,
hasPassword: user?.passwordHash !== null,
};
}
async deleteAccount(userId: string, password: string) {
// Get user with password hash
const user = await this.prisma.user.findUnique({
where: { id: userId },
select: {
passwordHash: true,
},
});
if (!user) {
throw new BadRequestException('User not found');
}
if (!user.passwordHash) {
throw new BadRequestException(
'Cannot delete account without password. Please set a password first.',
);
}
// Verify password
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
throw new UnauthorizedException('Incorrect password');
}
// Delete related data first (to avoid foreign key constraint errors)
// Delete AuthAccount records
await this.prisma.authAccount.deleteMany({
where: { userId: userId },
});
// Delete other related data if any
// Add more deleteMany calls here for other tables that reference User
// Finally, delete the user
await this.prisma.user.delete({
where: { id: userId },
});
return {
success: true,
message: 'Account deleted successfully',
};
}
}