Skip to content

Commit

Permalink
feat: find all and search contacts
Browse files Browse the repository at this point in the history
  • Loading branch information
n9mi committed Sep 16, 2024
1 parent 77bf481 commit a01fbe9
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 16 deletions.
136 changes: 125 additions & 11 deletions __tests__/contact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,121 @@ import { basePath, web } from "../src/application/web";
import logger from "../src/application/logger";
import { Contact } from "@prisma/client";

describe("GET /contact:id", () => {
describe("GET /contacts", () => {
let token: string = "";
let createdContact: Contact = {} as Contact;

beforeAll(async () => {
token = await ContactTestUtil.getToken();
createdContact = await ContactTestUtil.createContact();
});

afterAll(async () => {
await ContactTestUtil.deleteContact();
await ContactTestUtil.deleteUser();
});

it("should be able to get all contact", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(1);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.page_size).toBe(10);
});

it("should be able to search contacts by name - found", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.query({
name: "ast"
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(1);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.page_size).toBe(10);
});

it("should be able to search contacts by name - not found", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.query({
name: "random"
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(0);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(0);
expect(res.body.paging.page_size).toBe(10);
});

it("should be able to search contacts by email - found", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.query({
email: "t.co"
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(1);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.page_size).toBe(10);
});

it("should be able to search contacts by phone - found", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.query({
phone: "081"
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(1);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.page_size).toBe(10);
});

it("should be able to search contacts by phone - not found", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts`)
.query({
phone: "99"
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data).toBeInstanceOf(Array);
expect(res.body.data.length).toBe(0);
expect(res.body.paging.current_page).toBe(1);
expect(res.body.paging.total_page).toBe(0);
expect(res.body.paging.page_size).toBe(10);
});
});

describe("GET /contacts:id", () => {
let token: string = "";
let createdContact: Contact = {} as Contact;

Expand All @@ -21,7 +135,7 @@ describe("GET /contact:id", () => {

it ("should return 200 - success getting a contact", async () => {
const res = await supertest(web)
.get(`${basePath}/contact/${createdContact.id}`)
.get(`${basePath}/contacts/${createdContact.id}`)
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
Expand All @@ -35,7 +149,7 @@ describe("GET /contact:id", () => {

it("should return 404 - invalid contact id", async () => {
const res = await supertest(web)
.get(`${basePath}/contact/test123`)
.get(`${basePath}/contacts/test123`)
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
Expand All @@ -45,7 +159,7 @@ describe("GET /contact:id", () => {

it("should return 404 - contact id doesn't exists", async () => {
const res = await supertest(web)
.get(`${basePath}/contact/10000`)
.get(`${basePath}/contacts/10000`)
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
Expand All @@ -55,15 +169,15 @@ describe("GET /contact:id", () => {

it("should return 401 - empty authorization", async () => {
const res = await supertest(web)
.get(`${basePath}/contact/${createdContact.id}`);
.get(`${basePath}/contacts/${createdContact.id}`);

logger.info(res.body);
expect(res.status).toBe(401);
expect(res.body.errors).toBeDefined();
});
});

describe("POST /contact", () => {
describe("POST /contacts", () => {
let token: string = "";

beforeAll(async () => {
Expand All @@ -77,7 +191,7 @@ describe("POST /contact", () => {

it("should return 200 - success creating contact", async () => {
const res = await supertest(web)
.post(`${basePath}/contact`)
.post(`${basePath}/contacts`)
.send(ContactTestUtil.contact)
.set('Authorization', `Bearer ${token}`);

Expand All @@ -92,7 +206,7 @@ describe("POST /contact", () => {

it("should return 200 - success creating contact with emty last_name, email, and phone", async () => {
const res = await supertest(web)
.post(`${basePath}/contact`)
.post(`${basePath}/contacts`)
.send({
first_name: ContactTestUtil.contact.first_name
})
Expand All @@ -109,7 +223,7 @@ describe("POST /contact", () => {

it("should return 400 - bad request invalid email", async () => {
const res = await supertest(web)
.post(`${basePath}/contact`)
.post(`${basePath}/contacts`)
.send({
first_name: ContactTestUtil.contact.first_name,
last_name: ContactTestUtil.contact.last_name,
Expand All @@ -124,7 +238,7 @@ describe("POST /contact", () => {

it("should return 400 - bad request phone number more than 20 characters", async () => {
const res = await supertest(web)
.post(`${basePath}/contact`)
.post(`${basePath}/contacts`)
.send({
first_name: ContactTestUtil.contact.first_name,
last_name: ContactTestUtil.contact.last_name,
Expand All @@ -139,7 +253,7 @@ describe("POST /contact", () => {

it("should return 401 - empty authorization", async () => {
const res = await supertest(web)
.post(`${basePath}/contact`)
.post(`${basePath}/contacts`)
.send({
first_name: ContactTestUtil.contact.first_name
});
Expand Down
20 changes: 19 additions & 1 deletion src/controller/contact.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { Request, Response, NextFunction } from "express";
import ContactService from "../service/contact";
import { ContactRequest } from "../model/contact";
import { ContactRequest, ContactSearchRequest } from "../model/contact";
import { ResponseError } from "../error/response";

export class ContactController {

static async getAll(req: Request, res: Response, next: NextFunction) {
try {
const getReq: ContactSearchRequest = {
name: req.query.name as string,
email: req.query.email as string,
phone: req.query.phone as string,
page: req.query.page ? Number(req.query.page) : 1,
page_size: req.query.page_size ? Number(req.query.page_size) : 10
};
const getRes = await ContactService.findAll(res.locals.user.username, getReq);

res.status(200)
.json(getRes);
} catch (e) {
next(e);
}
}

static async getById(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.id))) {
Expand Down
8 changes: 8 additions & 0 deletions src/model/contact.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Contact } from "@prisma/client"

export interface ContactSearchRequest {
name?: string,
email?: string,
phone?: string,
page: number,
page_size: number
}

export interface ContactRequest {
first_name: string,
last_name: string,
Expand Down
10 changes: 10 additions & 0 deletions src/model/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Paging {
page_size: number,
total_page: number,
current_page: number
}

export interface Pageable<T> {
data: Array<T>
paging: Paging
}
5 changes: 3 additions & 2 deletions src/router/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { accessValidation } from "../middleware/accessValidation";
export const getContactRouter = (basePath: string) => {
const contactRouter = express.Router();
contactRouter.use(accessValidation);
contactRouter.get(`${basePath}/contact/:id`, ContactController.getById);
contactRouter.post(`${basePath}/contact`, ContactController.create);
contactRouter.get(`${basePath}/contacts`, ContactController.getAll);
contactRouter.get(`${basePath}/contacts/:id`, ContactController.getById);
contactRouter.post(`${basePath}/contacts`, ContactController.create);

return contactRouter;
}
84 changes: 83 additions & 1 deletion src/service/contact.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,93 @@
import { Contact } from "@prisma/client";
import prisma from "../application/database";
import { ResponseError } from "../error/response";
import { ContactRequest, ContactResponse, toContactResponse } from "../model/contact";
import { ContactRequest, ContactResponse, ContactSearchRequest, toContactResponse } from "../model/contact";
import { Pageable } from "../model/page";
import { ContactValidation } from "../validation/contact";
import { Validation } from "../validation/validation";

export default class ContactService {

static async findAll(username: string, req: ContactSearchRequest): Promise<Pageable<ContactResponse>> {
const validatedReq = Validation.validate(ContactValidation.SEARCH, req);
const skip = (validatedReq.page - 1) * validatedReq.page_size;

const filters = [];
if (validatedReq.name) {
filters.push({
OR: [
{
first_name: {
contains: validatedReq.name,
// mode: "insensitive",
}
},
{
last_name: {
contains: validatedReq.name,
// mode: "insensitive"
}
}
]
})
}
if (validatedReq.email) {
filters.push({
email: {
contains: validatedReq.email
}
});
}
if (validatedReq.phone) {
filters.push({
phone: {
contains: validatedReq.phone
}
});
}

let contacts: Contact[] = [];
let total: number = 0;
if (filters.length > 0) {
contacts = await prisma.contact.findMany({
where: {
username: username,
AND: filters
},
take: validatedReq.page_size,
skip: skip
});
total = await prisma.contact.count({
where: {
username: username,
AND: filters
},
});
} else {
contacts = await prisma.contact.findMany({
where: {
username: username
},
take: validatedReq.page_size,
skip: skip
});
total = await prisma.contact.count({
where: {
username: username
},
});
}

return {
data: contacts.map(c => toContactResponse(c)),
paging: {
current_page: validatedReq.page,
total_page: Math.ceil(total / validatedReq.page_size),
page_size: validatedReq.page_size
}
};
}

static async findById(username: string, id: number): Promise<ContactResponse> {
const contact = await prisma.contact.findFirstOrThrow({
where: {
Expand Down
Loading

0 comments on commit a01fbe9

Please sign in to comment.