🧩 Modules📝 Core📝 Core DatabaseThe contract that provides methods for database.
This is an abstract class and is used as a base for other modules.
Imports
Contract Interface
Copy import { CoreDatabaseContract } from 'niro-health' ;
Copy import type { ICoreDatabaseContract } from 'niro-health' ;
Method of Use
To implement this class, you will need an create entity.
Copy 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.
Copy 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.
Copy 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.
Copy 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;
}
}
Copy 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
Copy import { User } from './users/entities' ;
import { File } from './files/entities' ;
export type EntityWithRelation = User & {
files : File [];
};
files/types/entityWithRelation.ts
Copy 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.
Copy // 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" )
}
Copy 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
Instance of Random service.
Instance of StringEx service.
Instance of Crypto service.
Methods
Compare hash by text with hashed text.
Generate hash for password.
Compare hash for password with hashed password.
Encrypt data string to base64.
Decrypt encrypted base64 to string.