← back to dothanhdat2012202__xe16cho

Function bodies 503 total

All specs Real LLM only Function bodies
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 })),
    };

    awa
getUsers 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, n
toggleUserActive 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.t
All 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.t
SystemSettings 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.co
register 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'),
    );
  }
‹ prevpage 3 / 11next ›