Files
tabungin/apps/api/src/users/users.service.ts
dwindown 89f881e7cf feat: reorganize admin settings with tabbed interface and documentation
- Reorganized admin settings into tabbed interface (General, Security, Payment Methods)
- Vertical tabs on desktop, horizontal scrollable on mobile
- Moved Payment Methods from separate menu to Settings tab
- Fixed admin profile reuse and dashboard blocking
- Fixed maintenance mode guard to use AppConfig model
- Added admin auto-redirect after login (admins → /admin, users → /)
- Reorganized documentation into docs/ folder structure
- Created comprehensive README and documentation index
- Added PWA and Web Push notifications to to-do list
2025-10-13 09:28:12 +07:00

116 lines
2.9 KiB
TypeScript

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 {
constructor(private prisma: PrismaService) {}
async me() {
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',
};
}
}