- 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
116 lines
2.9 KiB
TypeScript
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',
|
|
};
|
|
}
|
|
}
|