Function bodies 503 total
toggleCompanyActive method · typescript · L101-L103 (3 LOC)backend/src/modules/admin/admin.controller.ts
toggleCompanyActive(@CurrentUser() user: any, @Param('id', ParseIntPipe) id: number) {
return this.adminService.toggleCompanyActive(id, user);
}getActivityLogs method · typescript · L108-L113 (6 LOC)backend/src/modules/admin/admin.controller.ts
getActivityLogs(
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
return this.adminService.getActivityLogs(+(page || '1'), +(limit || '30'));
}getSystemStats method · typescript · L118-L120 (3 LOC)backend/src/modules/admin/admin.controller.ts
getSystemStats() {
return this.adminService.getSystemStats();
}getLogFiles method · typescript · L132-L134 (3 LOC)backend/src/modules/admin/admin.controller.ts
getLogFiles() {
return this.appLogger.listLogFiles();
}readLog method · typescript · L138-L145 (8 LOC)backend/src/modules/admin/admin.controller.ts
readLog(
@Query('date') date: string,
@Query('type') type?: string,
@Query('lines') lines?: string,
) {
const d = date || new Date().toISOString().split('T')[0];
return this.appLogger.readLogFile(d, (type as any) || 'all', +(lines || '300'));
}constructor method · typescript · L17-L28 (12 LOC)backend/src/modules/admin/admin.service.ts
constructor(
@InjectRepository(User) private userRepo: Repository<User>,
@InjectRepository(Booking) private bookingRepo: Repository<Booking>,
@InjectRepository(Vehicle) private vehicleRepo: Repository<Vehicle>,
@InjectRepository(Payment) private paymentRepo: Repository<Payment>,
@InjectRepository(Subscription) private subRepo: Repository<Subscription>,
@InjectRepository(SystemSettings) private settingsRepo: Repository<SystemSettings>,
@InjectRepository(ActivityLog) private logRepo: Repository<ActivityLog>,
@InjectRepository(Company) private companyRepo: Repository<Company>,
private cache: RedisCacheService,
private trackingGateway: TrackingGateway,
) {}getDashboard method · typescript · L30-L71 (42 LOC)backend/src/modules/admin/admin.service.ts
async getDashboard() {
const cacheKey = 'admin:dashboard';
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const [totalUsers, totalDrivers, totalCustomers] = await Promise.all([
this.userRepo.count(),
this.userRepo.count({ where: { role: UserRole.DRIVER } }),
this.userRepo.count({ where: { role: UserRole.CUSTOMER } }),
]);
const [totalBookings, completedBookings, pendingBookings, cancelledBookings] =
await Promise.all([
this.bookingRepo.count(),
this.bookingRepo.count({ where: { status: BookingStatus.COMPLETED } }),
this.bookingRepo.count({ where: { status: BookingStatus.PENDING } }),
this.bookingRepo.count({ where: { status: BookingStatus.CANCELLED } }),
]);
const totalVehicles = await this.vehicleRepo.count();
const revenueResult = await this.paymentRepo
.createQueryBuilder('p')
.select('SUM(p.amount)', 'total')
.where('p.status = :status', {Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
getRevenueByPeriod method · typescript · L73-L107 (35 LOC)backend/src/modules/admin/admin.service.ts
async getRevenueByPeriod(startDate: string, endDate: string) {
const cacheKey = `admin:revenue:${startDate}:${endDate}`;
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const start = new Date(startDate);
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);
const payments = await this.paymentRepo.find({
select: ['id', 'amount', 'createdAt'],
where: {
status: PaymentStatus.COMPLETED,
createdAt: Between(start, end),
},
order: { createdAt: 'ASC' },
});
const total = payments.reduce((sum, p) => sum + Number(p.amount), 0);
const daily: Record<string, number> = {};
payments.forEach((p) => {
const day = p.createdAt.toISOString().split('T')[0];
daily[day] = (daily[day] || 0) + Number(p.amount);
});
const result = {
total,
count: payments.length,
daily: Object.entries(daily).map(([date, amount]) => ({ date, amount })),
};
awagetUsers method · typescript · L109-L166 (58 LOC)backend/src/modules/admin/admin.service.ts
async getUsers(page: number = 1, limit: number = 20, role?: string) {
const cacheKey = `admin:users:${page}:${limit}:${role || 'all'}`;
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const qb = this.userRepo
.createQueryBuilder('user')
.select([
'user.id', 'user.fullName', 'user.phone', 'user.email',
'user.role', 'user.avatarUrl', 'user.companyId',
'user.isActive', 'user.createdAt', 'user.updatedAt',
])
.orderBy('user.createdAt', 'DESC')
.skip((page - 1) * limit)
.take(limit);
if (role) qb.where('user.role = :role', { role });
const [items, total] = await qb.getManyAndCount();
// Lấy tên nhà xe từ companyId (driver/owner)
const companyIds = [...new Set(items.filter((u) => u.companyId).map((u) => u.companyId))];
let companyMap: Record<number, string> = {};
if (companyIds.length > 0) {
const companies = await this.userRepo.query(
`SELECT id, ntoggleUserActive method · typescript · L168-L179 (12 LOC)backend/src/modules/admin/admin.service.ts
async toggleUserActive(userId: number) {
const user = await this.userRepo.findOne({
select: ['id', 'fullName', 'phone', 'email', 'role', 'isActive'],
where: { id: userId },
});
if (!user) return null;
user.isActive = !user.isActive;
const saved = await this.userRepo.save(user);
await this.cache.delByPattern('admin:users:*');
await this.cache.del('admin:dashboard');
return saved;
}getBookings method · typescript · L181-L206 (26 LOC)backend/src/modules/admin/admin.service.ts
async getBookings(page: number = 1, limit: number = 20, status?: string) {
const cacheKey = `admin:bookings:${page}:${limit}:${status || 'all'}`;
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const where: any = {};
if (status) where.status = status;
const [items, total] = await this.bookingRepo.findAndCount({
select: [
'id', 'bookingCode', 'companyId', 'customerId', 'vehicleId',
'bookingType', 'status', 'pickupAddress', 'dropoffAddress',
'pickupTime', 'passengerCount', 'estimatedPrice', 'finalPrice',
'distanceKm', 'isPaid', 'cancelReason', 'createdAt',
],
where,
relations: ['customer', 'vehicle', 'vehicle.driver'],
order: { createdAt: 'DESC' },
skip: (page - 1) * limit,
take: limit,
});
const result = { items, total, page, totalPages: Math.ceil(total / limit) };
await this.cache.set(cacheKey, result, 60);
return result;
}getAllSettings method · typescript · L210-L220 (11 LOC)backend/src/modules/admin/admin.service.ts
async getAllSettings(): Promise<Record<string, string>> {
const cacheKey = 'system:all-settings';
const cached = await this.cache.get<Record<string, string>>(cacheKey);
if (cached) return cached;
const rows = await this.settingsRepo.find({ select: ['key', 'value'] });
const map: Record<string, string> = {};
rows.forEach((r) => { map[r.key] = r.value; });
await this.cache.set(cacheKey, map, 300);
return map;
}getPublicConfig method · typescript · L222-L246 (25 LOC)backend/src/modules/admin/admin.service.ts
async getPublicConfig() {
const cacheKey = 'system:public-config';
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const s = await this.getAllSettings();
const config = {
plans: {
free: { maxVehicles: +(s.free_max || '3'), price: +(s.free_price || '0') },
basic: { maxVehicles: +(s.basic_max || '10'), price: +(s.basic_price || '99000') },
pro: { maxVehicles: +(s.pro_max || '30'), price: +(s.pro_price || '79000') },
enterprise: { maxVehicles: +(s.enterprise_max || '9999'), price: +(s.enterprise_price || '59000') },
},
bank: {
bank: s.bank_name || 'MB Bank',
bin: s.bank_bin || '970422',
accountNumber: s.bank_account || '20120283869999',
accountName: s.bank_holder || 'DO THANH DAT',
branch: s.bank_branch || 'Chi nhánh TP.HN',
},
};
await this.cache.set(cacheKey, config, 300);
return config;
}saveSettings method · typescript · L248-L263 (16 LOC)backend/src/modules/admin/admin.service.ts
async saveSettings(data: Record<string, string>, user?: any): Promise<Record<string, string>> {
for (const [key, value] of Object.entries(data)) {
const existing = await this.settingsRepo.findOne({ select: ['id', 'key', 'value'], where: { key } });
if (existing) {
existing.value = value;
await this.settingsRepo.save(existing);
} else {
await this.settingsRepo.save(this.settingsRepo.create({ key, value }));
}
}
await this.cache.del('system:settings');
await this.cache.del('system:public-config');
await this.cache.del('system:all-settings');
if (user) this.writeLog(user, 'update_settings', 'Cập nhật cấu hình hệ thống');
return this.getAllSettings();
}getSubscriptionStats method · typescript · L267-L312 (46 LOC)backend/src/modules/admin/admin.service.ts
async getSubscriptionStats() {
const cacheKey = 'admin:sub-stats';
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const subs = await this.subRepo.find({
select: ['id', 'companyId', 'planType', 'totalAmount', 'status', 'createdAt'],
order: { createdAt: 'DESC' },
});
// Thống kê theo plan
const byPlan: Record<string, { count: number; revenue: number }> = {};
subs.forEach((s) => {
if (!byPlan[s.planType]) byPlan[s.planType] = { count: 0, revenue: 0 };
byPlan[s.planType].count++;
byPlan[s.planType].revenue += Number(s.totalAmount) || 0;
});
// Thống kê theo tháng (6 tháng gần nhất)
const monthly: Record<string, { count: number; revenue: number }> = {};
subs.forEach((s) => {
const month = new Date(s.createdAt).toISOString().slice(0, 7);
if (!monthly[month]) monthly[month] = { count: 0, revenue: 0 };
monthly[month].count++;
monthly[month].revenue += Number(s.tAll rows scored by the Repobility analyzer (https://repobility.com)
getPendingSubscriptions method · typescript · L316-L324 (9 LOC)backend/src/modules/admin/admin.service.ts
async getPendingSubscriptions() {
const subs = await this.subRepo.find({
select: ['id', 'companyId', 'planType', 'vehicleCount', 'totalAmount', 'billingCycle', 'status', 'createdAt'],
where: { status: SubscriptionStatus.PENDING_PAYMENT },
relations: ['company'],
order: { createdAt: 'DESC' },
});
return subs;
}approveSubscription method · typescript · L326-L347 (22 LOC)backend/src/modules/admin/admin.service.ts
async approveSubscription(id: number, user: any) {
const sub = await this.subRepo.findOne({ select: ['id', 'companyId', 'planType', 'vehicleCount', 'status'], where: { id }, relations: ['company'] });
if (!sub) return null;
sub.status = SubscriptionStatus.ACTIVE;
await this.subRepo.save(sub);
// Cập nhật company plan — maxVehicles = số xe owner đăng ký, không dùng giới hạn mặc định plan
await this.companyRepo.update(sub.companyId, {
planType: sub.planType as PlanType,
maxVehicles: sub.vehicleCount,
...(sub.endDate ? { subscriptionExpiry: new Date(sub.endDate) } : {}),
});
this.writeLog(user, 'approve_subscription', `Duyệt gói ${sub.planType} cho ${(sub as any).company?.name || sub.companyId}`, 'subscription', id);
await this.cache.delByPattern('admin:sub-stats');
await this.cache.del('admin:system-stats');
await this.cache.delByPattern('company:*');
// Notify owner
this.notifyOwner(sub.companyId, 'Gói đã được kírejectSubscription method · typescript · L349-L360 (12 LOC)backend/src/modules/admin/admin.service.ts
async rejectSubscription(id: number, user: any) {
const sub = await this.subRepo.findOne({ select: ['id', 'companyId', 'planType', 'status'], where: { id }, relations: ['company'] });
if (!sub) return null;
sub.status = SubscriptionStatus.CANCELLED;
await this.subRepo.save(sub);
this.writeLog(user, 'reject_subscription', `Từ chối gói ${sub.planType} cho ${(sub as any).company?.name || sub.companyId}`, 'subscription', id);
// Notify owner
this.notifyOwner(sub.companyId, 'Yêu cầu nâng cấp bị từ chối', `Yêu cầu nâng cấp gói ${sub.planType} đã bị từ chối. Vui lòng liên hệ hỗ trợ.`);
await this.cache.delByPattern('admin:sub-stats');
return sub;
}toggleCompanyActive method · typescript · L364-L375 (12 LOC)backend/src/modules/admin/admin.service.ts
async toggleCompanyActive(companyId: number, user: any) {
const company = await this.companyRepo.findOne({ select: ['id', 'name', 'isActive'], where: { id: companyId } });
if (!company) return null;
company.isActive = !company.isActive;
await this.companyRepo.save(company);
this.writeLog(user, company.isActive ? 'activate_company' : 'deactivate_company', `${company.isActive ? 'Mở khoá' : 'Khoá'} nhà xe ${company.name}`, 'company', companyId);
await this.cache.del('company:all');
await this.cache.del(`company:${companyId}`);
await this.cache.delByPattern('company:owner:*');
await this.cache.del('admin:system-stats');
return company;
}notifyOwner method · typescript · L379-L395 (17 LOC)backend/src/modules/admin/admin.service.ts
private async notifyOwner(companyId: number, title: string, body: string) {
try {
const owners = await this.userRepo.query(
`SELECT id FROM users WHERE companyId = ? AND role = 'owner'`, [companyId],
);
for (const o of owners) {
await this.userRepo.query(
`INSERT INTO notifications (userId, title, body, type, isRead, createdAt) VALUES (?, ?, ?, 'system', 0, NOW())`,
[o.id, title, body],
);
await this.cache.del(`notif:user:${o.id}`);
await this.cache.del(`notif:unread:${o.id}`);
// Socket realtime → toast + chuông cho owner
this.trackingGateway.emitToUser(o.id, 'subscription:status', { title, body });
}
} catch {}
}writeLog method · typescript · L397-L409 (13 LOC)backend/src/modules/admin/admin.service.ts
private async writeLog(user: any, action: string, detail: string, targetType?: string, targetId?: number) {
try {
await this.logRepo.save(this.logRepo.create({
userId: user.id || user.sub,
userName: user.fullName || 'System',
action,
detail,
targetType,
targetId,
}));
await this.cache.delByPattern('admin:activity-logs:*');
} catch {}
}getActivityLogs method · typescript · L411-L425 (15 LOC)backend/src/modules/admin/admin.service.ts
async getActivityLogs(page = 1, limit = 30) {
const cacheKey = `admin:activity-logs:${page}:${limit}`;
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const [items, total] = await this.logRepo.findAndCount({
select: ['id', 'userId', 'userName', 'action', 'detail', 'targetType', 'targetId', 'createdAt'],
order: { createdAt: 'DESC' },
skip: (page - 1) * limit,
take: limit,
});
const result = { items, total, page, totalPages: Math.ceil(total / limit) };
await this.cache.set(cacheKey, result, 30);
return result;
}getSystemStats method · typescript · L429-L457 (29 LOC)backend/src/modules/admin/admin.service.ts
async getSystemStats() {
const cacheKey = 'admin:system-stats';
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const [totalUsers, totalCompanies, totalBookings, totalVehicles] = await Promise.all([
this.userRepo.count(),
this.companyRepo.count(),
this.bookingRepo.count(),
this.vehicleRepo.count(),
]);
const activeSubs = await this.subRepo.count({ where: { status: SubscriptionStatus.ACTIVE } });
const revenueResult = await this.subRepo.createQueryBuilder('s')
.select('SUM(s.totalAmount)', 'total')
.where('s.status = :status', { status: SubscriptionStatus.ACTIVE })
.getRawOne();
const result = {
totalUsers,
totalCompanies,
totalBookings,
totalVehicles,
activeSubscriptions: activeSubs,
subscriptionRevenue: parseFloat(revenueResult?.total || '0'),
};
await this.cache.set(cacheKey, result, 60);
return result;
}Want this analysis on your repo? https://repobility.com/scan/
broadcastNotification method · typescript · L461-L488 (28 LOC)backend/src/modules/admin/admin.service.ts
async broadcastNotification(data: { title: string; message: string; target: string }, user: any) {
const where: any = {};
if (data.target === 'owner') where.role = 'owner';
else if (data.target === 'driver') where.role = 'driver';
else if (data.target === 'customer') where.role = 'customer';
const users = await this.userRepo.find({ select: ['id'], where });
// Tạo notifications bằng parameterized batch insert
if (users.length > 0) {
const placeholders = users.map(() => '(?, ?, ?, ?, 0, NOW())').join(',');
const params = users.flatMap((u) => [u.id, data.title, data.message, 'system']);
await this.userRepo.query(
`INSERT INTO notifications (userId, title, body, type, isRead, createdAt) VALUES ${placeholders}`,
params,
);
// Socket realtime → toast + chuông cho tất cả users
for (const u of users) {
this.cache.del(`notif:user:${u.id}`);
this.cache.del(`notif:unread:${u.id}`);
this.tSystemSettings class · typescript · L4-L16 (13 LOC)backend/src/modules/admin/system-settings.entity.ts
export class SystemSettings {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true, length: 50 })
key: string;
@Column({ type: 'text' })
value: string;
@UpdateDateColumn()
updatedAt: Date;
}AuthController class · typescript · L9-L67 (59 LOC)backend/src/modules/auth/auth.controller.ts
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Public()
@UseGuards(ThrottlerGuard)
@Throttle({ auth: { ttl: 60000, limit: 30 } })
@Post('register')
async register(@Body() dto: RegisterDto) {
return this.authService.register(dto);
}
@Public()
@UseGuards(ThrottlerGuard)
@Throttle({ auth: { ttl: 60000, limit: 30 } })
@Post('login')
async login(@Body() dto: LoginDto) {
return this.authService.login(dto);
}
@Public()
@UseGuards(ThrottlerGuard)
@Throttle({ auth: { ttl: 60000, limit: 30 } })
@Post('pre-login')
async preLogin(@Body() dto: LoginDto) {
return this.authService.preLogin(dto);
}
@Public()
@UseGuards(ThrottlerGuard)
@Throttle({ auth: { ttl: 60000, limit: 30 } })
@Post('verify-login')
async verifyLogin(@Body() body: { phone?: string; email?: string; code: string }) {
const identifier = body.email || body.phone || '';
return this.authService.verifyLogin(identifier, body.coregister method · typescript · L16-L18 (3 LOC)backend/src/modules/auth/auth.controller.ts
async register(@Body() dto: RegisterDto) {
return this.authService.register(dto);
}login method · typescript · L24-L26 (3 LOC)backend/src/modules/auth/auth.controller.ts
async login(@Body() dto: LoginDto) {
return this.authService.login(dto);
}preLogin method · typescript · L32-L34 (3 LOC)backend/src/modules/auth/auth.controller.ts
async preLogin(@Body() dto: LoginDto) {
return this.authService.preLogin(dto);
}getMe method · typescript · L46-L48 (3 LOC)backend/src/modules/auth/auth.controller.ts
async getMe(@CurrentUser('id') userId: number) {
return this.authService.getMe(userId);
}forgotPassword method · typescript · L54-L56 (3 LOC)backend/src/modules/auth/auth.controller.ts
async forgotPassword(@Body('email') email: string) {
return this.authService.forgotPassword(email);
}If a scraper extracted this row, it came from Repobility (https://repobility.com)
AuthService class · typescript · L27-L208 (182 LOC)backend/src/modules/auth/auth.service.ts
export class AuthService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly jwtService: JwtService,
private readonly otpService: OtpService,
private readonly cache: RedisCacheService,
private readonly configService: ConfigService,
) {}
async register(
dto: RegisterDto,
): Promise<{ token: string; user: Partial<User> }> {
if (dto.captchaToken) {
const valid = await this.verifyCaptcha(dto.captchaToken);
if (!valid) throw new BadRequestException('Xác thực reCAPTCHA thất bại');
}
const existingUser = await this.userRepository.findOne({
select: ['id', 'phone'],
where: { phone: dto.phone },
});
if (existingUser) {
throw new ConflictException('Số điện thoại đã được đăng ký');
}
const hashedPassword = await bcrypt.hash(dto.password, 10);
const user = this.userRepository.create({
fullName: dto.fullName,
phone: dto.phone,
constructor method · typescript · L28-L35 (8 LOC)backend/src/modules/auth/auth.service.ts
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly jwtService: JwtService,
private readonly otpService: OtpService,
private readonly cache: RedisCacheService,
private readonly configService: ConfigService,
) {}register method · typescript · L37-L39 (3 LOC)backend/src/modules/auth/auth.service.ts
async register(
dto: RegisterDto,
): Promise<{ token: string; user: Partial<User> }> {verifyCaptcha method · typescript · L72-L84 (13 LOC)backend/src/modules/auth/auth.service.ts
private async verifyCaptcha(token: string): Promise<boolean> {
try {
const res = await axios.post(
'https://www.google.com/recaptcha/api/siteverify',
null,
{ params: { secret: this.configService.get('RECAPTCHA_SECRET'), response: token } },
);
// v3: success + score >= 0.5
return res.data?.success === true && (res.data?.score ?? 1) >= 0.5;
} catch {
return false;
}
}findAndVerify method · typescript · L87-L115 (29 LOC)backend/src/modules/auth/auth.service.ts
private async findAndVerify(dto: LoginDto): Promise<User> {
if (dto.captchaToken) {
const valid = await this.verifyCaptcha(dto.captchaToken);
if (!valid) throw new BadRequestException('Xác thực reCAPTCHA thất bại');
}
if (!dto.phone && !dto.email) {
throw new BadRequestException('Vui lòng nhập số điện thoại hoặc email');
}
const qb = this.userRepository.createQueryBuilder('user').addSelect('user.password');
if (dto.email) {
qb.where('user.email = :email', { email: dto.email });
} else {
qb.where('user.phone = :phone', { phone: dto.phone });
}
const user = await qb.getOne();
if (!user) {
throw new UnauthorizedException('Tài khoản hoặc mật khẩu không đúng');
}
const isPasswordValid = await bcrypt.compare(dto.password, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Tài khoản hoặc mật khẩu không đúng');
}
return user;
}getMe method · typescript · L162-L178 (17 LOC)backend/src/modules/auth/auth.service.ts
async getMe(userId: number): Promise<User> {
const cacheKey = `user:${userId}`;
const cached = await this.cache.get<User>(cacheKey);
if (cached) return cached;
const user = await this.userRepository.findOne({
select: USER_SELECT,
where: { id: userId },
});
if (!user) {
throw new UnauthorizedException('Người dùng không tồn tại');
}
await this.cache.set(cacheKey, user, 120);
return user;
}generateToken method · typescript · L204-L207 (4 LOC)backend/src/modules/auth/auth.service.ts
private generateToken(user: User): string {
const payload = { sub: user.id, role: user.role, companyId: user.companyId || null };
return this.jwtService.sign(payload);
}LoginDto class · typescript · L3-L22 (20 LOC)backend/src/modules/auth/dto/login.dto.ts
export class LoginDto {
@IsOptional()
@IsString()
@MaxLength(15)
phone?: string;
@IsOptional()
@IsString()
@MaxLength(100)
email?: string;
@IsString()
@IsNotEmpty({ message: 'Mật khẩu không được để trống' })
@MaxLength(72)
password: string;
@IsOptional()
@IsString()
captchaToken?: string;
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
RegisterDto class · typescript · L13-L44 (32 LOC)backend/src/modules/auth/dto/register.dto.ts
export class RegisterDto {
@IsString()
@IsNotEmpty({ message: 'Họ tên không được để trống' })
@MinLength(2, { message: 'Họ tên phải có ít nhất 2 ký tự' })
@MaxLength(100, { message: 'Họ tên không được vượt quá 100 ký tự' })
fullName: string;
@IsString()
@IsNotEmpty({ message: 'Số điện thoại không được để trống' })
@Matches(/^0\d{8,10}$/, {
message: 'Số điện thoại không hợp lệ (VD: 0901234567)',
})
phone: string;
@IsString()
@IsNotEmpty({ message: 'Mật khẩu không được để trống' })
@MinLength(6, { message: 'Mật khẩu phải có ít nhất 6 ký tự' })
@MaxLength(72, { message: 'Mật khẩu không được vượt quá 72 ký tự' })
password: string;
@IsEmail({}, { message: 'Email không hợp lệ' })
@IsNotEmpty({ message: 'Email không được để trống' })
email: string;
@IsOptional()
@IsEnum(UserRole, { message: 'Role không hợp lệ' })
role?: UserRole;
@IsOptional()
@IsString()
captchaToken?: string;
}JwtAuthGuard class · typescript · L7-L21 (15 LOC)backend/src/modules/auth/guards/jwt-auth.guard.ts
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}constructor method · typescript · L8-L10 (3 LOC)backend/src/modules/auth/guards/jwt-auth.guard.ts
constructor(private reflector: Reflector) {
super();
}canActivate method · typescript · L12-L20 (9 LOC)backend/src/modules/auth/guards/jwt-auth.guard.ts
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}JwtStrategy class · typescript · L18-L46 (29 LOC)backend/src/modules/auth/jwt.strategy.ts
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
configService: ConfigService,
) {
const secret = configService.get<string>('JWT_SECRET');
if (!secret) {
throw new Error('JWT_SECRET is not defined in environment variables');
}
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: secret,
});
}
async validate(payload: JwtPayload): Promise<User> {
const user = await this.userRepository.findOne({
where: { id: payload.sub },
});
if (!user || !user.isActive) {
throw new UnauthorizedException('Tài khoản không hợp lệ hoặc đã bị vô hiệu hóa');
}
return user;
}
}constructor method · typescript · L19-L33 (15 LOC)backend/src/modules/auth/jwt.strategy.ts
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
configService: ConfigService,
) {
const secret = configService.get<string>('JWT_SECRET');
if (!secret) {
throw new Error('JWT_SECRET is not defined in environment variables');
}
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: secret,
});
}validate method · typescript · L35-L45 (11 LOC)backend/src/modules/auth/jwt.strategy.ts
async validate(payload: JwtPayload): Promise<User> {
const user = await this.userRepository.findOne({
where: { id: payload.sub },
});
if (!user || !user.isActive) {
throw new UnauthorizedException('Tài khoản không hợp lệ hoặc đã bị vô hiệu hóa');
}
return user;
}BookingController class · typescript · L19-L117 (99 LOC)backend/src/modules/booking/booking.controller.ts
export class BookingController {
constructor(private readonly bookingService: BookingService) {}
@Post()
create(@CurrentUser() user: User, @Body() dto: CreateBookingDto) {
return this.bookingService.create(user.id, dto);
}
@Get()
findAll(@CurrentUser() user: User) {
if (user.role === 'driver') {
return this.bookingService.findByDriver(user.id);
}
return this.bookingService.findByCustomer(user.id);
}
@Get('history')
findHistory(
@CurrentUser() user: User,
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
return this.bookingService.findHistory(
user.id,
user.role,
+(page || '1'),
+(limit || '10'),
);
}
@Get('stats')
getStats(@CurrentUser() user: User) {
return this.bookingService.getStats(user.id, user.role);
}
@Get('search')
searchByCode(@Query('code') code: string) {
return this.bookingService.findByCode(code);
}
@Get('seat-map/:vehicleId')
getSeatMap(
All rows scored by the Repobility analyzer (https://repobility.com)
create method · typescript · L23-L25 (3 LOC)backend/src/modules/booking/booking.controller.ts
create(@CurrentUser() user: User, @Body() dto: CreateBookingDto) {
return this.bookingService.create(user.id, dto);
}findAll method · typescript · L28-L33 (6 LOC)backend/src/modules/booking/booking.controller.ts
findAll(@CurrentUser() user: User) {
if (user.role === 'driver') {
return this.bookingService.findByDriver(user.id);
}
return this.bookingService.findByCustomer(user.id);
}findHistory method · typescript · L36-L47 (12 LOC)backend/src/modules/booking/booking.controller.ts
findHistory(
@CurrentUser() user: User,
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
return this.bookingService.findHistory(
user.id,
user.role,
+(page || '1'),
+(limit || '10'),
);
}