Skip to content

Commit

Permalink
feat: create address
Browse files Browse the repository at this point in the history
  • Loading branch information
n9mi committed Sep 17, 2024
1 parent 6e0b8fe commit 3c09308
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 22 deletions.
161 changes: 161 additions & 0 deletions __tests__/address.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import prisma from "../src/application/database";
import bcrypt from "bcrypt";
import supertest from "supertest";
import { basePath, web } from "../src/application/web";
import { Address, Contact } from "@prisma/client";
import logger from "../src/application/logger";

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

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

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

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

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data.id).toBeGreaterThanOrEqual(1);
expect(res.body.data.street).toBe(AddressTestUtil.address.street);
expect(res.body.data.city).toBe(AddressTestUtil.address.city);
expect(res.body.data.province).toBe(AddressTestUtil.address.province);
expect(res.body.data.country).toBe(AddressTestUtil.address.country);
expect(res.body.data.postal_code).toBe(AddressTestUtil.address.postal_code);
});

it("should return 200 - success creating an address with empty street, city, and province", async () => {
const res = await supertest(web)
.post(`${basePath}/contacts/${contact.id}/addresses`)
.send({
country: AddressTestUtil.address.country,
postal_code: AddressTestUtil.address.postal_code,
})
.set('Authorization', `Bearer ${token}`);

logger.info(res.body);
expect(res.status).toBe(200);
expect(res.body.data.id).toBeGreaterThanOrEqual(1);
expect(res.body.data.street).toBe("");
expect(res.body.data.city).toBe("");
expect(res.body.data.province).toBe("");
expect(res.body.data.country).toBe(AddressTestUtil.address.country);
expect(res.body.data.postal_code).toBe(AddressTestUtil.address.postal_code);
});

it("should return 400 - invalid postal code", async () => {
const res = await supertest(web)
.post(`${basePath}/contacts/${contact.id}/addresses`)
.send({
country: AddressTestUtil.address.country,
postal_code: "123",
})
.set('Authorization', `Bearer ${token}`);

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

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

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

class AddressTestUtil {
static user = {
name: "user_test_address",
username: "user_test_address",
password: "password"
};

static contact = {
first_name: "First",
last_name: "Last",
email: "[email protected]",
phone: "08123456789"
};

static address = {
street: "Test Street",
city: "Test City",
province: "Test Province",
country: "Test Country",
postal_code: "123456",
}

static async createUser() {
await prisma.user.create({
data: {
name: AddressTestUtil.user.name,
username: AddressTestUtil.user.username,
password: await bcrypt.hash(AddressTestUtil.user.password, 10)
}
});
}

static async getToken() {
await AddressTestUtil.createUser();

const loginRes = await supertest(web)
.post(`${basePath}/auth/login`)
.send({
username: AddressTestUtil.user.username,
password: AddressTestUtil.user.password,
});

return loginRes.body.data.token;
}

static async deleteUser() {
await prisma.user.deleteMany({});
}

static async createContact(): Promise<Contact> {
const contact = await prisma.contact.create({
data: {
...AddressTestUtil.contact,
username: AddressTestUtil.user.username
}
});

return contact as Contact;
}

static async deleteContact() {
await prisma.contact.deleteMany({});
}

static async createAddress(contactId: number): Promise<Address> {
const address = await prisma.address.create({
data: {
...AddressTestUtil.address,
contact_id: contactId
}
});

return address as Address;
}

static async deleteAddress() {
await prisma.address.deleteMany({});
}
}
4 changes: 2 additions & 2 deletions __tests__/contact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ class ContactTestUtil {
phone: "08123456789"
}

static async create() {
static async createUser() {
await prisma.user.create({
data: {
name: ContactTestUtil.user.name,
Expand All @@ -437,7 +437,7 @@ class ContactTestUtil {
}

static async getToken() {
await ContactTestUtil.create();
await ContactTestUtil.createUser();

const loginRes = await supertest(web)
.post(`${basePath}/auth/login`)
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
2 changes: 2 additions & 0 deletions src/application/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getErrorMiddleware } from "../middleware/error";
import dotenv from "dotenv";
import { getUserRouter } from "../router/user";
import { getContactRouter } from "../router/contact";
import { getAddressRouter } from "../router/address";

dotenv.config();
export const basePath = process.env.BASE_URL_PATH === undefined ? "/api/v1" : process.env.BASE_URL_PATH;
Expand All @@ -13,4 +14,5 @@ web.use(express.json());
web.use(getAuthRouter(basePath));
web.use(getUserRouter(basePath));
web.use(getContactRouter(basePath));
web.use(getAddressRouter(basePath));
web.use(getErrorMiddleware());
27 changes: 27 additions & 0 deletions src/controller/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response, NextFunction } from "express";
import AddressService from "../service/address";
import { AddressRequest } from "../model/address";
import { ResponseError } from "../error/response";

export class AddressController {

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 createRes = await AddressService.create(res.locals.user.usename, createReq);

return res.status(200)
.json({
data: createRes
});
} catch (e) {
next(e);
}
}
}
6 changes: 3 additions & 3 deletions src/controller/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class ContactController {

static async getById(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.id))) {
if (isNaN(Number(req.params.id)) || Number(req.params.id) < 1) {
throw new ResponseError(404, "contact doesn't exists")
}
const getRes = await ContactService.findById(res.locals.user.username, Number(req.params.id));
Expand Down Expand Up @@ -54,7 +54,7 @@ export class ContactController {

static async update(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.id))) {
if (isNaN(Number(req.params.id)) || Number(req.params.id) < 1) {
throw new ResponseError(404, "contact doesn't exists");
}
const updateRes = await ContactService.update(res.locals.user.username,
Expand All @@ -72,7 +72,7 @@ export class ContactController {

static async delete(req: Request, res: Response, next: NextFunction) {
try {
if (isNaN(Number(req.params.id))) {
if (isNaN(Number(req.params.id)) || Number(req.params.id) < 1) {
throw new ResponseError(404, "contact doesn't exists");
}
const deleteRes = await ContactService.delete(res.locals.username, Number(req.params.id));
Expand Down
30 changes: 30 additions & 0 deletions src/model/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Address } from "@prisma/client";

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

export interface AddressResponse {
id: number,
street?: string,
city?: string,
province?: string,
country: string,
postal_code: string,
}

export function toAddressResponse(address: Address): AddressResponse {
return {
id: address.id,
street: address.street ?? "",
city: address.city ?? "",
province: address.province ?? "",
country: address.country,
postal_code: address.postal_code
}
}
11 changes: 11 additions & 0 deletions src/router/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import express from "express";
import { accessValidation } from "../middleware/accessValidation";
import { AddressController } from "../controller/address";

export const getAddressRouter = (basePath: string) => {
const addressRouter = express.Router();
addressRouter.use(accessValidation);
addressRouter.post(`${basePath}/contacts/:contactId/addresses`, AddressController.create);

return addressRouter;
}
30 changes: 30 additions & 0 deletions src/service/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import prisma from "../application/database";
import { ResponseError } from "../error/response";
import { AddressRequest, toAddressResponse } from "../model/address";
import { AddressValidation } from "../validation/address";
import { Validation } from "../validation/validation";

export default class AddressService {

// static async

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

const isContactExists = await prisma.contact.count({
where: {
id: validateReq.contact_id,
username: username
}
}) === 1;
if (!isContactExists) {
throw new ResponseError(404, "contact doesn't exists");
}

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

return toAddressResponse(address);
}
}
Loading

0 comments on commit 3c09308

Please sign in to comment.