Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/address book #36

Merged
merged 11 commits into from
Nov 17, 2023
Prev Previous commit
Next Next commit
feat(address-book): add address-book module
phbarao committed Nov 1, 2023
commit 8076716cf49df87ded57425c2ed2ca084502cc82
85 changes: 85 additions & 0 deletions src/modules/addressBook/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Role from '@src/models/Role';

import { error } from '@utils/error';
import { Responses, bindMethods, successful } from '@utils/index';

import { IUserService } from '../configs/user/types';
import {
IAddressBookService,
ICreateAddressBookRequest,
IDeleteAddressBookRequest,
IListAddressBookRequest,
IUpdateAddressBookRequest,
} from './types';

export class AddressBookController {
private addressBookService: IAddressBookService;
private userService: IUserService;

constructor(addressBookService: IAddressBookService, userService: IUserService) {
Object.assign(this, { addressBookService, userService });
bindMethods(this);
}

async create({ body, user: { provider } }: ICreateAddressBookRequest) {
try {
const { address } = body;
const roles = await Role.find({ where: [{ name: 'Administrador' }] });

let user = await this.userService.findByAddress(address);

if (!user) {
user = await this.userService.create({
address,
provider,
role: roles[0],
avatar: await this.userService.randomAvatar(),
active: true,
});
}

const newContact = await this.addressBookService.create({
...body,
createdBy: user,
});
return successful(newContact, Responses.Ok);
} catch (e) {
return error(e.error, e.statusCode);
}
}

async update({ body, params }: IUpdateAddressBookRequest) {
try {
const updatedContact = await this.addressBookService.update(params.id, body);
return successful(updatedContact, Responses.Ok);
} catch (e) {
return error(e.error, e.statusCode);
}
}

async delete({ params }: IDeleteAddressBookRequest) {
try {
const deletedContact = await this.addressBookService.delete(params.id);
return successful(deletedContact, Responses.Ok);
} catch (e) {
return error(e.error, e.statusCode);
}
}

async list({ query, user }: IListAddressBookRequest) {
const { id } = user;
const { orderBy, sort, page, perPage, q } = query;

try {
const response = await this.addressBookService
.filter({ createdBy: id, q })
.ordination({ orderBy, sort })
.paginate({ page, perPage })
.list();

return successful(response, Responses.Ok);
} catch (e) {
return error(e.error, e.statusCode);
}
}
}
30 changes: 30 additions & 0 deletions src/modules/addressBook/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Router } from 'express';

import { authMiddleware } from '@src/middlewares';

import { handleResponse } from '@utils/index';

import { UserService } from '../configs/user/service';
import { AddressBookController } from './controller';
import { AddressBookService } from './services';
import {
validateCreateAddressBookPayload,
validateUpdateAddressBookPayload,
} from './validations';

const router = Router();
const addressBookService = new AddressBookService();
const userService = new UserService();
const { create, update, list, delete: deleteContact } = new AddressBookController(
addressBookService,
userService,
);

router.use(authMiddleware);

router.post('/', validateCreateAddressBookPayload, handleResponse(create));
router.put('/:id', validateUpdateAddressBookPayload, handleResponse(update));
router.delete('/:id', handleResponse(deleteContact));
router.get('/', handleResponse(list));

export default router;
155 changes: 155 additions & 0 deletions src/modules/addressBook/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Brackets } from 'typeorm';

import AddressBook from '@src/models/AddressBook';
import { NotFound } from '@src/utils/error';

import GeneralError, { ErrorTypes } from '@utils/error/GeneralError';
import Internal from '@utils/error/Internal';
import { IOrdination, setOrdination } from '@utils/ordination';
import { IPagination, Pagination, PaginationParams } from '@utils/pagination';

import {
IAddressBookService,
ICreateAddressBookPayload,
IFilterAddressBookParams,
IUpdateAddressBookPayload,
} from './types';

export class AddressBookService implements IAddressBookService {
private _ordination: IOrdination<AddressBook> = {
orderBy: 'updatedAt',
sort: 'DESC',
};
private _pagination: PaginationParams;
private _filter: IFilterAddressBookParams;
filter(filter: IFilterAddressBookParams) {
this._filter = filter;
return this;
}

paginate(pagination?: PaginationParams) {
this._pagination = pagination;
return this;
}

ordination(ordination?: IOrdination<AddressBook>) {
this._ordination = setOrdination(ordination);
return this;
}

async create(payload: ICreateAddressBookPayload): Promise<AddressBook> {
return await AddressBook.create(payload)
.save()
.then(contact => contact)
.catch(e => {
throw new Internal({
type: ErrorTypes.Internal,
title: 'Error on contact creation',
detail: e,
});
});
}

async list(): Promise<IPagination<AddressBook> | AddressBook[]> {
const hasPagination = this._pagination?.page && this._pagination?.perPage;
const queryBuilder = AddressBook.createQueryBuilder('ab')
.select(['ab.id', 'ab.nickname'])
.innerJoin('ab.user', 'user')
.innerJoin('ab.createdBy', 'createdBy')
.addSelect(['user.id', 'user.address', 'createdBy.id', 'createdBy.address']);

const handleInternalError = e => {
if (e instanceof GeneralError) throw e;

throw new Internal({
type: ErrorTypes.Internal,
title: 'Error on predicate list',
detail: e,
});
};

this._filter.createdBy &&
queryBuilder.andWhere('ab.created_by = :createdBy', {
createdBy: `${this._filter.createdBy}`,
});

this._filter.q &&
queryBuilder.andWhere(
new Brackets(qb =>
qb
.where('LOWER(ab.nickname) LIKE LOWER(:nickname)', {
nickname: `%${this._filter.q}%`,
})
.orWhere('LOWER(user.address) LIKE LOWER(:address)', {
address: `%${this._filter.q}%`,
}),
),
);

queryBuilder.orderBy(`ab.${this._ordination.orderBy}`, this._ordination.sort);

return hasPagination
? Pagination.create(queryBuilder)
.paginate(this._pagination)
.then(result => result)
.catch(handleInternalError)
: queryBuilder
.getMany()
.then(predicates => predicates)
.catch(handleInternalError);
}

async findById(id: string): Promise<AddressBook> {
return AddressBook.findOne({
where: { id },
// relations: ['assets', 'witnesses', 'predicate'],
})
.then(contact => {
if (!contact) {
throw new NotFound({
type: ErrorTypes.NotFound,
title: 'Contact not found',
detail: `No contact was found for the provided ID: ${id}.`,
});
}

return contact;
})
.catch(e => {
if (e instanceof GeneralError) throw e;

throw new Internal({
type: ErrorTypes.Internal,
title: 'Error on transaction findById',
detail: e,
});
});
}

async update(
id: string,
payload: IUpdateAddressBookPayload,
): Promise<AddressBook> {
return AddressBook.update({ id }, payload)
.then(() => this.findById(id))
.catch(e => {
throw new Internal({
type: ErrorTypes.Internal,
title: 'Error on transaction update',
detail: e,
});
});
}

async delete(id: string) {
return await AddressBook.update({ id }, { deletedAt: new Date() })
.then(() => true)
.catch(() => {
throw new NotFound({
type: ErrorTypes.NotFound,
title: 'Contact not found',
detail: `Contact with id ${id} was not found`,
});
});
}
}
77 changes: 77 additions & 0 deletions src/modules/addressBook/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ContainerTypes, ValidatedRequestSchema } from 'express-joi-validation';

import AddressBook from '@src/models/AddressBook';

import { User } from '@models/index';

import { AuthValidatedRequest } from '@middlewares/auth/types';

import { IOrdination } from '@utils/ordination';
import { IPagination, PaginationParams } from '@utils/pagination';

export enum OrderBy {
nickname = 'nickname',
creation = 'createdAt',
update = 'updatedAt',
}

export enum Sort {
asc = 'ASC',
desc = 'DESC',
}

export interface ICreateAddressBookPayload {
nickname: string;
address: string;
createdBy: User;
}

export type IUpdateAddressBookPayload = Omit<
ICreateAddressBookPayload,
'createdBy'
>;

export interface IFilterAddressBookParams {
q?: string;
createdBy?: string;
}

interface ICreateAddressBookRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Body]: ICreateAddressBookPayload;
}

interface IUpdateAddressBookRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Body]: IUpdateAddressBookPayload;
[ContainerTypes.Params]: { id: string };
}

interface IDeleteAddressBookRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Params]: { id: string };
}

interface IListAddressBookRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Query]: {
q: string;
createdBy: string;
orderBy: OrderBy;
sort: Sort;
page: string;
perPage: string;
};
}

export type ICreateAddressBookRequest = AuthValidatedRequest<ICreateAddressBookRequestSchema>;
export type IUpdateAddressBookRequest = AuthValidatedRequest<IUpdateAddressBookRequestSchema>;
export type IDeleteAddressBookRequest = AuthValidatedRequest<IDeleteAddressBookRequestSchema>;
export type IListAddressBookRequest = AuthValidatedRequest<IListAddressBookRequestSchema>;

export interface IAddressBookService {
ordination(ordination?: IOrdination<AddressBook>): this;
paginate(pagination?: PaginationParams): this;
filter(filter: IFilterAddressBookParams): this;

create: (payload: ICreateAddressBookPayload) => Promise<AddressBook>;
update: (id: string, payload: IUpdateAddressBookPayload) => Promise<AddressBook>;
delete: (id: string) => Promise<boolean>;
list: () => Promise<IPagination<AddressBook> | AddressBook[]>;
}
17 changes: 17 additions & 0 deletions src/modules/addressBook/validations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Joi from 'joi';

import { validator } from '@utils/index';

export const validateCreateAddressBookPayload = validator.body(
Joi.object({
nickname: Joi.string().required(),
address: Joi.string().required(),
}),
);

export const validateUpdateAddressBookPayload = validator.body(
Joi.object({
nickname: Joi.string().required(),
address: Joi.string().required(),
}),
);
2 changes: 2 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Router } from 'express';

import addressBook from '@modules/addressBook/routes';
import auth from '@modules/auth/routes';
import roles from '@modules/configs/roles/routes';
import users from '@modules/configs/user/routes';
@@ -15,6 +16,7 @@ router.use('/user', users);
router.use('/predicate', predicates);
router.use('/transaction', transactions);
router.use('/template', vaultTemplate);
router.use('/address-book', addressBook);

// ping route
router.get('/ping', ({ res }) =>