All files / src/platform/biometric biometric-hub.service.ts

0% Statements 0/29
0% Branches 0/24
0% Functions 0/5
0% Lines 0/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                                                                                                                                                                                                           
import { Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import {
  BiometricDevice,
  BiometricEmployeeMapping,
  BiometricRawLog,
} from './schemas/biometric.schema';
import { EventBusService } from '../events/event-bus.service';
 
@Injectable()
export class BiometricHubService {
  private readonly logger = new Logger(BiometricHubService.name);
 
  constructor(
    @InjectModel(BiometricDevice.name)
    private readonly deviceModel: Model<BiometricDevice>,
    @InjectModel(BiometricEmployeeMapping.name)
    private readonly mappingModel: Model<BiometricEmployeeMapping>,
    @InjectModel(BiometricRawLog.name)
    private readonly logModel: Model<BiometricRawLog>,
    private readonly eventBus: EventBusService,
  ) {}
 
  async registerDevice(
    tenantId: string,
    params: {
      deviceId: string;
      name: string;
      brand: 'ZKTeco' | 'eSSL' | 'Suprema' | 'Matrix';
      ipAddress: string;
      port?: number;
    },
  ): Promise<BiometricDevice> {
    const existing = await this.deviceModel
      .findOne({ tenantId, deviceId: params.deviceId })
      .exec();
    const doc =
      existing ?? new this.deviceModel({ tenantId, deviceId: params.deviceId });
 
    doc.name = params.name;
    doc.brand = params.brand;
    doc.ipAddress = params.ipAddress;
    doc.port = params.port || 4370;
    doc.status = 'connected';
 
    await doc.save();
    return doc;
  }
 
  async mapEmployee(
    tenantId: string,
    userId: string,
    biometricUserId: string,
  ): Promise<BiometricEmployeeMapping> {
    return this.mappingModel
      .findOneAndUpdate(
        { tenantId, userId },
        { biometricUserId },
        { upsert: true, new: true },
      )
      .exec();
  }
 
  async ingestLog(
    tenantId: string,
    deviceId: string,
    biometricUserId: string,
    timestamp: Date,
    verifyMode: number,
    inOutMode: number,
  ): Promise<void> {
    const raw = await this.logModel.create({
      tenantId,
      deviceId,
      biometricUserId,
      timestamp,
      verifyMode,
      inOutMode,
    });
 
    // Check mapping to resolve internal User ID
    const mapping = await this.mappingModel
      .findOne({ tenantId, biometricUserId })
      .exec();
    if (mapping) {
      await this.eventBus.publish(
        'platform.biometric.log.normalized.v1',
        {
          tenantId,
          userId: mapping.userId,
          biometricUserId,
          deviceId,
          timestamp,
          inOutMode: inOutMode === 0 ? 'CHECK_IN' : 'CHECK_OUT',
        },
        tenantId,
      );
    } else {
      this.logger.warn(
        `Biometric user mapping not found for: ${biometricUserId}`,
      );
    }
  }
 
  async getDevices(tenantId: string): Promise<BiometricDevice[]> {
    return this.deviceModel.find({ tenantId }).exec();
  }
}