Skip to content

Commit

Permalink
feat: address get all
Browse files Browse the repository at this point in the history
  • Loading branch information
n9mi committed Sep 17, 2024
1 parent 3c09308 commit d366ec6
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 35 deletions.
69 changes: 69 additions & 0 deletions __tests__/address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,75 @@ import { basePath, web } from "../src/application/web";
import { Address, Contact } from "@prisma/client";
import logger from "../src/application/logger";

describe("GET /contacts/:contactId/addresses", () => {
let token: string = "";
let contact: Contact = {} as Contact;
let address: Address = {} as Address;

beforeAll(async () => {
token = await AddressTestUtil.getToken();
contact = await AddressTestUtil.createContact();
address = await AddressTestUtil.createAddress(contact.id);
});

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

it("should be able to get all address", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts/${contact.id}/addresses`)
.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.page_size).toBe(10);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.current_page).toBe(1);
});

it("should be able to get all address - pagination", async () => {
const res = await supertest(web)
.get(`${basePath}/contacts/${contact.id}/addresses`)
.query({
page: 2,
page_size: 5
})
.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.page_size).toBe(5);
expect(res.body.paging.total_page).toBe(1);
expect(res.body.paging.current_page).toBe(2);
});

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

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

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

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

describe("POST /contacts/:contactId/addresses", () => {
let token: string = "";
let contact: Contact = {} as Contact;
Expand Down
34 changes: 17 additions & 17 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ services:
- POSTGRES_PASSWORD=${DB_PASSWORD}
networks:
- contact_api_network
# contact-api:
# environment:
# - DATABASE_URL=${DATABASE_URL}
# - PORT=${PORT}
# - BASE_URL_PATH=${BASE_URL_PATH}
# - JWT_SECRET=${JWT_SECRET}
# - JWT_EXPIRED_IN_MINUTES=${JWT_EXPIRED_IN_MINUTES}
# build: .
# image: contact-api
# ports:
# - '3000:3000'
# depends_on:
# - db
# volumes:
# - .:/usr/src/node-app
# networks:
# - contact_api_network
contact-api:
environment:
- DATABASE_URL=${DATABASE_URL}
- PORT=${PORT}
- BASE_URL_PATH=${BASE_URL_PATH}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRED_IN_MINUTES=${JWT_EXPIRED_IN_MINUTES}
build: .
image: contact-api
ports:
- '3000:3000'
depends_on:
- db
volumes:
- .:/usr/src/node-app
networks:
- contact_api_network

volumes:
postgres-db:
Expand Down
23 changes: 21 additions & 2 deletions src/controller/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,35 @@ import { ResponseError } from "../error/response";

export class AddressController {

static async getAll(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.contactId)) || Number(req.params.contactId) < 1) {
throw new ResponseError(404, "contact doesn't exists");
}

const getReq = {
page: req.query.page ? Number(req.query.page) : 1,
page_size: req.query.page_size ? Number(req.query.page_size) : 10
};
const getRes = await AddressService.findAll(res.locals.user.usename, Number(req.params.contactId), getReq);

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

static async create(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.contactId)) || Number(req.params.contactId) < 1) {
throw new ResponseError(404, "contact doesn't exists");
}

const createReq = req.body as AddressRequest;
createReq.contact_id = Number(req.params.contactId);
const contactId = Number(req.params.contactId);

const createRes = await AddressService.create(res.locals.user.usename, createReq);
const createRes = await AddressService.create(res.locals.user.usename, contactId, createReq);

return res.status(200)
.json({
Expand Down
8 changes: 6 additions & 2 deletions src/model/address.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Address } from "@prisma/client";

export interface AddressListRequest {
page: number,
page_size: number
}

export interface AddressRequest {
street?: string,
city?: string,
province?: string,
country: string,
postal_code: string,
contact_id: number
postal_code: string
}

export interface AddressResponse {
Expand Down
1 change: 1 addition & 0 deletions src/router/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AddressController } from "../controller/address";
export const getAddressRouter = (basePath: string) => {
const addressRouter = express.Router();
addressRouter.use(accessValidation);
addressRouter.get(`${basePath}/contacts/:contactId/addresses`, AddressController.getAll);
addressRouter.post(`${basePath}/contacts/:contactId/addresses`, AddressController.create);

return addressRouter;
Expand Down
49 changes: 44 additions & 5 deletions src/service/address.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
import prisma from "../application/database";
import { ResponseError } from "../error/response";
import { AddressRequest, toAddressResponse } from "../model/address";
import { AddressListRequest, AddressRequest, AddressResponse, toAddressResponse } from "../model/address";
import { Pageable } from "../model/page";
import { AddressValidation } from "../validation/address";
import { Validation } from "../validation/validation";

export default class AddressService {

// static async
static async findAll(username: string, contactId: number, req: AddressListRequest): Promise<Pageable<AddressResponse>> {
const validatedReq = Validation.validate(AddressValidation.LIST, req);

static async create(username: string, req: AddressRequest) {
const isContactExists = await prisma.contact.count({
where: {
id: contactId,
username: username
}
}) === 1;
if (!isContactExists) {
throw new ResponseError(404, "contact doesn't exists");
}

const skip = (validatedReq.page - 1) * validatedReq.page_size;
const addresses = await prisma.address.findMany({
where: {
contact_id: contactId,
},
take: validatedReq.page_size,
skip: skip
});
const total = await prisma.address.count({
where: {
contact_id: contactId
}
});

return {
data: addresses.map(a => toAddressResponse(a)),
paging: {
current_page: validatedReq.page,
total_page: Math.ceil(total / validatedReq.page_size),
page_size: validatedReq.page_size
}
}
}

static async create(username: string, contactId: number, req: AddressRequest) {
const validateReq = Validation.validate(AddressValidation.SAVE, req);

const isContactExists = await prisma.contact.count({
where: {
id: validateReq.contact_id,
id: contactId,
username: username
}
}) === 1;
Expand All @@ -22,7 +58,10 @@ export default class AddressService {
}

const address = await prisma.address.create({
data: validateReq
data: {
...validateReq,
contact_id: contactId
}
});

return toAddressResponse(address);
Expand Down
24 changes: 16 additions & 8 deletions src/validation/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ import { z, ZodType } from "zod";

export class AddressValidation {

static readonly LIST: ZodType = z.object({
page: z.number({
invalid_type_error: "page should be a number"
}).min(1, {
message: "page should be at least equal to 1"
}).positive(),
page_size: z.number({
invalid_type_error: "page_size should be a number"
}).min(1, {
message: "page should be at least equal to 1"
}).max(100, {
message: "page_size can't be more than 100"
}).positive(),
});

static readonly SAVE: ZodType = z.object({
street: z.string({
invalid_type_error: "street should be string"
Expand Down Expand Up @@ -39,13 +54,6 @@ export class AddressValidation {
message: "postal code length should more than or equal 5 characters"
}).max(20, {
message: "postal code length should less than 20 characters"
}),
contact_id: z.number({
invalid_type_error: "contact id should be a number"
}).min(1, {
message: "contact should more than 1"
}).positive({
message: "contact should be a positive number"
}),
})
});
}
2 changes: 1 addition & 1 deletion src/validation/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class ContactValidation {
message: "email length should more than 1 character"
}).optional(),
page: z.number({
invalid_type_error: "page should be a number"
invalid_type_error: "page should be a number"
}).min(1, {
message: "page should be at least equal to 1"
}).positive(),
Expand Down

0 comments on commit d366ec6

Please sign in to comment.