Part 3. Clock-in/out System: Basic backend (II) - UsersModule

JavaScript TypeScript NodeJS NestJS

This post is part of a Series of post which I'm describing a clock-in/out system if you want to read more you can read the following posts:


In the previous post, I presented the basic backend structure and the first module (AuthModule). So, I recommend you that you read that post before this because you can understand the whole system.

This post will be presented te UsersModule which is used to manage the user information. The service UserService provide two important methods:

1. getUsersWithoutKey
2. addUser

These methods are used to know that user have not a valid ID Card and add an user to the system.

So, the first step is show the structure of files of the UsersModule which is shown in Figure 1.

Figure 1. UsersModule structure

In the develop of this module, we've use the same directory structure that the used in AuthModule  but we've a new directory called controllers which is used to communicate this module with the exterior using an APIRestful. Furthermore, you can see two entities because in this module we need two tables (User  and UserSchedule).

So, the module's file is shown in the following code:

users.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { UserService } from './services/users.service';
import { UserProviders } from './entities/users.provider';
import { UserController } from './controllers/user.controller';

@Module({
  imports: [DatabaseModule],
  providers: [UserService, ...UserProviders],
  controllers: [UserController],
  exports: [UserService],
})
export class UsersModule {}

This module only import DatabaseModule to connect with our Postgres using TypeORM and export the UserService which is used in AppController. In this module is defined the controller UserController which will be used to communicate this module with the exterior.

Entities

In this module we need use two entities:

  • User: This entity define the user information.
  • Scheduler: This entity define the scheduler of the user (that's a  weak-entity).

So, the first step to define the entities is defined the provider which allow used the UserRepository in our services by injection.

user.providers.ts
import { Connection } from 'typeorm';
import { User } from './user.entity';
import {
  DB_CONNECTION_TOKEN,
  USER_REPOSITORY_TOKEN,
} from '../../../common/config/database.tokens.constants';

export const UserProviders = [
  {
    provide: USER_REPOSITORY_TOKEN,
    useFactory: (connection: Connection) => connection.getRepository(User),
    inject: [DB_CONNECTION_TOKEN],
  },
];


So, the first entity is user which is defined by the following fields:

  • uid: UID of the user, in this case is a string by the "surname, name" of the user.
  • name: Name of the user, this field is used to show the name in a screen.
  • auth: That's the relation between the table Auth and Users. This field is a list of Authentication of the user.
  • key: The key which is assign to any user.
  • schedule: That's one of the most important field because it is the relation between the user and their schedule. The second entity of the user's module is that.
user.entity.ts
import { Entity, Column, OneToMany, PrimaryColumn } from 'typeorm';
import { UserSchedule } from './user-schedule.entity';
import { AuthEntity } from '../../auth/entities/auth.entity';
@Entity()
export class User {
  @Column()
  @PrimaryColumn()
  uid: string;

  @Column({ nullable: false })
  name: string;

  @OneToMany(type => AuthEntity, auth => auth.user)
  auths: AuthEntity[];

  @Column({
    nullable: true,
  })
  key: string;

  @OneToMany(type => UserSchedule, userSchedule => userSchedule.user)
  schedule: UserSchedule[];
}

The UserSchedule entity is used to reflect the fact that each session in which the user must be in the building. The fields which are store in this table are the following:

  • UID: The UserSchedule's UID. This field is automatically generated by the database.
  • day: The day of the week in which the user must be in the building (from 0 to 6 is the equivalent to from Sunday to Saturday).
  • hour: The hour of the day in which the user must be in the building (from 0 to 11 is the equivalent to from 8:15 to 22.10, but the relation is not lineal but there is a function which do that task).
  • room: The space in which the user must be in that hour.
  • user: The relation between the table UserSchedule and User. Many UserSchedule are related with a User.
user-schedule.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class UserSchedule {
  @Column()
  @PrimaryGeneratedColumn()
  uid: string;

  @Column()
  day: string;

  @Column()
  hour: string;

  @Column()
  room: string;

  @ManyToOne(() => User, user => user.schedule)
  user: User;
}

Finally, the system  is composed of 3 tables:

  • User: The information about the users in the system and their keys.
  • User-Schedule: The information about the schedule and rooms where the user must be.
  • Auth: The information about the clock-in/out (included the timestamp)

Constants and DTOs

The next section is very easy as in the previously post. In this section we define the constants and DTOs to obtain a better code. The constants are used to clean the code of strings or numbers while DTOs are used to validate the user from client-side.

In the file user.constants.ts you can see several arrays:

  • SCHEDULE_EXCLUDE: The list of scheduler which will be exclude to the list (must the user be in the building).
  • SCHEDULE_HOURS: The different hours to start and end the session of the user in
  • Several constants to export formats of moment or the first and last hour in different work shifts.
user.constants.ts
export const SCHEDULE_EXCLUDE = ['Mayor de 55 años', 'FCT'];

export const SCHEDULE_HOURS = [
  ['8:15', '9:15'],
  ['9:15', '10:15'],
  ['10:15', '11:15'],
  ['11:45', '12:45'],
  ['12:45', '13:45'],
  ['13:45', '14:45'],
  ['16:00', '17:00'],
  ['17:00', '18:00'],
  ['18:00', '19:00'],
  ['19:00', '20:00'],
  ['20:15', '21:15'],
  ['21:15', '22:15'],
];

export const FIRST_HOUR_MONING = '8:15';
export const FIRST_HOUR_NIGHT = '16:00';
export const LAST_HOUR_MORNING = '14:45';
export const LAST_HOUR_NIGHT = '22:15';
export const HOUR_FORMAT = 'HH:mm';

export const HOUR_START_NIGHT = 6;

The user.dto file is very simple too. In this file you can see  the definition of a class in which is defined two fields (UID and name).

user.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';

export class UserDto {
  uid: string;
  @IsString()
  @IsNotEmpty()
  name: string;

  constructor(user: any) {
    this.uid = user.uid;
    this.name = user.name;
  }
}

Controllers

Now is the moment to introduced the user's controller. In this file you can see that the controller is called user and two verbs are used:

  • GET /user: This method invoke the method getUsersWithoutKey from the service to obtain all users which are not key in the system (that's used to fill the information from a client-side).
  • POST /user: This method invoke the method addUser from the service to add the key to an user. In fact, the body of the POST should be an uid and key.
user.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { User } from '../entities/user.entity';
import { UserService } from '../services/users.service';

@Controller('user')
export class UserController {
  constructor(private userService: UserService) {}

  @Get()
  getUsers(): Promise {
    return this.userService.getUsersWithoutKey();
  }

  @Post()
  addUser(@Body() userDto: { uid: string; key: string }): Promise {
    return this.userService.addUser(userDto);
  }
}

Services

Finally, the most important of this module is the service due to logic of module is inside this file. So, the UserService has three important methods:

  • getUsersWithoutKey: In this method the return value is a Promise of UserEntity[] from TypeORM. So, the target of this method is invoke the correct SELECT sentence using the ORM which consist in all users which are NULL the key value.
  • addUser: In this method the return value is a Promise which is returned from the method save of TypeORM. So, addUser is a wrapper of TypeORM which is a wrapper of the INSERT/UPDATE sentence.
  • getUsersWorkingNow: This method are not used inside the UsersModule but that is used from AppController. This method return a Promise of UsersEntity[] which is componed of all users which must be in the building in now moment. This method used the library MomentJS. This method would be done in bad-code with a lot of code smells but I've preferred  used several variables to clean the code. Furthermore, I've used a private function isMorning which allow to me know if the system is in the morning of afternoon. That's because there are several users which must be in the building in several hours in morning and several hours in afternoon. The Users returned contains the list of authentications in the day (using lower and upper limits).
user.service.ts
import { User as UserEntity, User } from '../entities/user.entity';
import { Injectable, Inject } from '@nestjs/common';
import { USER_REPOSITORY_TOKEN } from 'common/config/database.tokens.constants';
import { Repository } from 'typeorm';
import * as moment from 'moment';
import {
  SCHEDULE_EXCLUDE,
  SCHEDULE_HOURS,
  FIRST_HOUR_MORNING,
  FIRST_HOUR_NIGHT,
  LAST_HOUR_MORNING,
  LAST_HOUR_NIGHT,
  HOUR_FORMAT,
} from '../constants/users.constans';

@Injectable()
export class UserService {
  constructor(
    @Inject(USER_REPOSITORY_TOKEN)
    private readonly usersRepository: Repository< UserEntity >,
  ) {}

  public getUsersWithoutKey(): Promise< UserEntity[] > {
    return this.usersRepository
      .createQueryBuilder('user')
      .select('user.uid')
      .where('user.key IS NULL')
      .getMany();
  }
  public addUser(userDto: { uid: string; key: string }): Promise< any > {
    return this.usersRepository.save(Object.assign(new User(), userDto));
  }

  public getUsersMustBeWorkingNow() {
    const date = moment();
    const dayOfWeek = date.day() - 1;
    const hourNow = this.convertBetweenRealHourAndScheduleHour(date);
    const isMorning = this.isMorning(date);

    const users = this.usersRepository
      .createQueryBuilder('user')
      .innerJoinAndSelect('user.schedule', 'schedule')
      .leftJoinAndSelect(
        'user.auths',
        'auths',
        '(auths.timestamp > :lowerHour AND auths.timestamp < :upperHour)',
        {
          lowerHour: isMorning
            ? moment(FIRST_HOUR_MORNING, HOUR_FORMAT).unix()
            : moment(FIRST_HOUR_NIGHT, HOUR_FORMAT).unix(),
          upperHour: isMorning
            ? moment(LAST_HOUR_MORNING, HOUR_FORMAT).unix()
            : moment(LAST_HOUR_NIGHT, HOUR_FORMAT).unix(),
        },
      )
      .where('schedule.day = :dayOfWeek', {
        dayOfWeek,
      })
      .andWhere('schedule.hour = :hourNow', {
        hourNow,
      })
      .andWhere('schedule.room NOT IN (:...exclude)', {
        exclude: SCHEDULE_EXCLUDE,
      })
      .getMany();
    return users;
  }

  private convertBetweenRealHourAndScheduleHour(
    realHour: moment.Moment,
  ): number {
    return SCHEDULE_HOURS.findIndex(range =>
      realHour.isBetween(
        moment(range[0], HOUR_FORMAT),
        moment(range[1], HOUR_FORMAT),
      ),
    );
  }

  private isMorning(hour: moment.Moment): boolean {
    return hour.isBetween(moment(FIRST_HOUR_MORNING), moment(LAST_HOUR_MORNING));
  }
}

Resume

‌In this post I've explain my UsersModule which is very simple because I'm using clean code in my coding. This module is used to save the information about users and clock-in/out. The method getUsersMustBeWorkingNow is the main method of the system because this method return the list of users which must be in the building using several constraints. This constraints are easily customisable.

In the following post of this series I'm going to explain the AppModule which communicate the client-side with the server-side and the modules of the server-side between them.


The GitHub project is https://github.com/Caballerog/clock-in-out.
The GitHub branch of this post is https://github.com/Caballerog/clock-in-out/tree/part3-basic-backend-users.