📝Core Database

The contract that provides methods for database.

This is an abstract class and is used as a base for other modules.

Imports

import { CoreDatabaseContract } from 'niro-health';

Method of Use

To implement this class, you will need an create entity.

entities/index.ts
import { CoreEntityContract } from 'niro-health';

export class User extends CoreEntityContract {
  username: string;
  email: string;
  password: string;

  constructor(data: Partial<User>) {
    super(data);
    this.username = data?.username;
    this.email = data?.email;
    this.password = data?.password;
  }
}

Next, you can implement the contract.

contracts/index.ts
import { CoreDatabaseContract } from 'niro-health';
import type { User } from './entities';

export abstract class UserDatabaseContract extends CoreDatabaseContract<User> {
  abstract findByEmail(email: string): Promise<User | null>;
}

Let's create an in-memory bank by implementing our contract.

db/memory.ts
import type { INestApplication } from '@nestjs/common';
import type { ISimilarityFilterService, SimilarityFilterType as Type } from 'niro-health';
import type { User } from './entities';

import { UserDatabaseContract } from './contracts';

import * as _ from 'lodash';

export class UserMemoryDB extends UserDatacbaseContract {
  private readonly _similarityFilterService: ISimilarityFilterService;

  constructor(
    protected readonly app: INestApplication,
    private users: User[] = [],
  ) {
    super(app);
    this._similarityFilterService = this.app.get<ISimilarityFilterService>(
      'ISimilarityFilterService',
    );
  }

  async create(data: User): Promise<User> {
    this.users.push(data);

    return data;
  }

  async findAll(limit?: number, offset?: number): Promise<User[]> {
    return this.users.slice(offset || 0, limit || this.users.length);
  }

  async findOne(id: number | string): Promise<User | null> {
    return this.users.find((user) => user.id === id);
  }

  async findBy(filter: Partial<User>, similarity?: Type): Promise<User[]> {
    return this.users.filter((key) =>
      this._similarityFilterService.execute<User>(
        filter,
        key as User,
        similarity || 'full',
      ),
    ) as User[];
  }

  async findByEmail(email: string): Promise<User | null> {
    const hash = this.hashText(email);

    return this.users.find((user) => user.hash.email === hash);
  }

  async update(id: number | string, newData: User): Promise<User | null> {
    this.users = this.users.map((user) =>
      user.id === id ? { ...user, ..._.omitBy(newData, _.isNil) } : user,
    ) as User[];

    return await this.findOne(id);
  }

  async delete(id: number | string): Promise<boolean> {
    this.users = this.users.filter((user) => user.id !== id);

    return true;
  }
}

Entities with relationships

We know that in the development of clean architecture, we should not have coupling between entities, even if one depends on the other. Here is an example using two entities, users and files, and how we can create a type that creates a union between them so that this type can be used by ORMs.

First, let's create the entities.

users/entities/index.ts
import { CoreEntityContract } from 'niro-health';

export class User extends CoreEntityContract {
  username: string;
  email: string;
  password: string;
  hash: {
    email: string;
  };

  constructor(data: Partial<User>) {
    super(data);
    this.username = data?.username;
    this.email = data?.email;
    this.password = data?.password;
    this.hash = data?.hash;
  }
}
files/entities/index.ts
import { CoreEntityContract } from 'niro-health';

export class File extends CoreEntityContract {
  authorId: string;
  name: string;
  mimetype: string;

  constructor(data: Partial<User>) {
    super(data);
    this.authorId = data?.authorId;
    this.name = data?.name;
    this.mimetype = data?.mimetype;
  }
}

Now we will create the type to join the entities.

users/types/entityWithRelation.ts
import { User } from './users/entities';
import { File } from './files/entities';

export type EntityWithRelation = User & {
  files: File[];
};
files/types/entityWithRelation.ts
import { File } from './files/entities';
import { User } from './users/entities';

export type EntityWithRelation = File & {
  author: User;
};

Let's choose Prisma as the ORM to implement our database contract.

prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "linux-musl", "debian-openssl-1.1.x", "linux-musl-openssl-3.0.x"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String    @id @default(uuid())
  username  String    @unique
  email     String    @unique
  password  String
  hash      Json
  status    Int       @default(1) // 1: normal, 0: disabled
  expired   Boolean   @default(false)
  deleted   Boolean   @default(false)
  createdAt DateTime  @default(now())
  updatedAt DateTime  @default(now())
  expiredAt DateTime?
  deletedAt DateTime?

  files File[]

  @@map("users")
}

model File {
  id             String    @id @default(uuid())
  authorId       String
  name           String
  mimetype       String
  status         Int       @default(1) // 1: normal, 0: disabled
  expired        Boolean   @default(false)
  deleted        Boolean   @default(false)
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @default(now())
  expiredAt      DateTime?
  deletedAt      DateTime?

  author User @relation(fields: [authorId], references: [id])

  @@map("files")
}
users/db/prisma.ts
import type { INestApplication } from '@nestjs/common';
import type { 
  RecursivePartial,
  ISimilarityFilterService,
  SimilarityFilterType as Type,
  PrismaService
} from 'niro-health';
import type { User } from './users/entities';
import type { EntityWithRelation } from './users/types/entityWithRelation';

import { UserDatabaseContract } from './users/contracts';

import * as _ from 'lodash';

export class UserPrismaDB extends UserDatabaseContract {
  private readonly _prismaService: PrismaService;
  private readonly _similarityFilterService: ISimilarityFilterService;

  constructor(protected readonly app: INestApplication) {
    super(app);
    this._prismaService = this.app.get<PrismaService>('IPrismaService');
    this._similarityFilterService = this.app.get<ISimilarityFilterService>(
      'ISimilarityFilterService',
    );
  }

  async create(data: User): Promise<EntityWithRelation> {
    return (await this._prismaService.user.create({
      data: {
        id: data.id as string,
        username: data.username,
        email: data.email,
        password: data.password,
        hash: data.hash,
      },
      include: {
        files: true,
      },
    })) as EntityWithRelation;
  }

  async findAll(limit?: number, skip?: number): Promise<EntityWithRelation[]> {
    return (await this._prismaService.user.findMany({
      skip,
      take: limit,
      include: {
        files: true,
      },
    })) as EntityWithRelation[];
  }

  async findOne(id: string): Promise<EntityWithRelation | null> {
    return (await this._prismaService.user.findFirst({
      where: {
        id: id ?? '',
      },
      include: {
        files: true,
      },
    })) as EntityWithRelation;
  }

  async findBy(
    filter: RecursivePartial<EntityWithRelation>,
    similarity?: Type,
  ): Promise<EntityWithRelation[]> {
    const users = await this._prismaService.user.findMany({
      include: {
        files: true,
      },
    });

    return users.filter((user) =>
      this._similarityFilterService.execute<EntityWithRelation>(
        filter,
        user as EntityWithRelation,
        similarity || 'full',
      ),
    ) as EntityWithRelation[];
  }

  async findByEmail(email: string): Promise<EntityWithRelation | null> {
    const hash = this.hashText(email);

    return (await this._prismaService.user.findFirst({
      where: {
        hash: {
          path: ['email'],
          equals: hash,
        },
      },
      include: {
        files: true,
      },
    })) as EntityWithRelation;
  }

  async update(id: string, newData: User): Promise<EntityWithRelation | null> {
    let data = _.omitBy(newData, _.isNil);
    data = _.omit(data, ['files']);
    return (await this._prismaService.user.update({
      where: { id },
      data,
      include: {
        files: true,
      },
    })) as EntityWithRelation;
  }

  async delete(id: string): Promise<boolean> {
    if ((await this._prismaService.user.count()) <= 0) return false;

    const user = await this._prismaService.user.delete({
      where: { id },
    });

    if (!user) return false;

    return true;
  }
}

Our implementation is complete.

Properties

Property
Scope
Description

_randomService

protected

Instance of Random service.

_stringExService

protected

Instance of StringEx service.

_cryptoService

protected

Instance of Crypto service.

Methods

Method
Scope
Description

generateUUID

public

Generate UUID.

hashText

public

Generate hash by text.

compareHashText

public

Compare hash by text with hashed text.

hashPassword

public

Generate hash for password.

compareHashPassword

public

Compare hash for password with hashed password.

encrypt

public

Encrypt data string to base64.

decrypt

public

Decrypt encrypted base64 to string.

create

abstract

Create new model.

findAll

abstract

Find all models.

findOne

abstract

Find one model by id.

findBy

abstract

Find models by filter.

update

abstract

Update model by id.

delete

abstract

Delete model by id.

Last updated