Skip to content

Commit

Permalink
🎉 Added Story API end point and tests, Added missing RoleController T…
Browse files Browse the repository at this point in the history
…ests, Added Story Test init Sql for migration & seeding
  • Loading branch information
shalomsam committed Mar 9, 2018
1 parent 1409941 commit 63e24dc
Show file tree
Hide file tree
Showing 15 changed files with 827 additions and 44 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@types/express": "^4.11.1",
"@types/helmet": "0.0.37",
"@types/jsonwebtoken": "^7.2.5",
"@types/marked": "^0.3.0",
"@types/mocha": "^2.2.48",
"@types/node": "^9.4.6",
"@types/nodemailer": "^4.3.4",
Expand Down Expand Up @@ -96,6 +97,7 @@
"helmet": "^3.12.0",
"https": "^1.0.0",
"jsonwebtoken": "^8.2.0",
"marked": "^0.3.17",
"moment": "^2.21.0",
"nodemailer": "^4.6.0",
"nunjucks": "^3.1.2",
Expand Down
4 changes: 4 additions & 0 deletions src/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,9 @@
"user": "user@gmail.com",
"pass": "pass"
}
},
"marked": {
"gfm": true,
"table": true
}
}
93 changes: 59 additions & 34 deletions src/server/controllers/BaseController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,14 @@ export class BaseController {
const self = this;

let limit = queryParams !== undefined && queryParams.hasOwnProperty("limit") ? queryParams.limit : 100;
let count = queryParams !== undefined &&
queryParams.hasOwnProperty("count") ? queryParams.count : await this.modelObj.count();

limit = parseInt(limit, 10);
count = parseInt(count, 10);

const totalPages = limit > count ? 1 : (count / limit);
const currentPage = queryParams !== undefined && queryParams.hasOwnProperty("page") ? queryParams.page : 1;
const nextPage = currentPage === totalPages ? currentPage : (currentPage + 1);
const prevPage = currentPage === 1 ? 1 : (currentPage - 1);
let next, prev;
next = prev = req.path + "?";
next = `${next}page=${nextPage}&limit=${limit}&count=${count}&totalPages=${totalPages}`;
prev = `${prev}page=${prevPage}&limit=${limit}&count=${count}&totalPages=${totalPages}`;

const pagination = {
currentPage,
totalPages,
count,
limit,
next,
prev,
};

this.setPagination(pagination);

limit = parseInt(limit, 10);
const skip = (currentPage - 1) * limit;

this.setPagination(req, queryParams);

this.setUp(req, res);
return this.modelObj.find({ skip, take: limit })
return this.modelObj.find({ skip, take: limit, relations: this.getRelations() })
.then(self.handleSuccess.bind(self))
.catch(self.handleError.bind(self));
}
Expand All @@ -96,10 +74,10 @@ export class BaseController {
: Promise<express.Response> {

this.setUp(req, res);
return this.modelObj.findOne({id})
return this.modelObj.findOne({ where: { id } , relations: this.getRelations() })
.then((response) => {
if (response === undefined) {
throw new HttpError(404, "User Not found");
throw new HttpError(404, `Entity(${this.modelName}) Not found`);
}
return response;
})
Expand Down Expand Up @@ -172,10 +150,10 @@ export class BaseController {
return this.modelObj.findOne({id})
.then((entity) => {
return entity.remove()
.then(this.handleSuccess)
.catch(this.handleError);
.then(this.handleSuccess.bind(this))
.catch(this.handleError.bind(this));
})
.catch(this.handleError);
.catch(this.handleError.bind(this));
}

/**
Expand Down Expand Up @@ -276,8 +254,15 @@ export class BaseController {

const obj = {};
let modelName = this.modelName.toLowerCase();
const lastY = modelName.length - 1;

// add plural (to key name) for arrays
modelName = (response instanceof Array) ? modelName + "s" : modelName;
if (response instanceof Array) {
// replace last occurance of "y" with "ies" else only append "s"
modelName = modelName[lastY] === "y" ?
modelName.replace(/y(?![\w]*y)/, "ies") : modelName + "s";
}

// sanitize entity with its exclude list
this.sanitize(response, false);
obj[modelName] = response;
Expand Down Expand Up @@ -310,8 +295,34 @@ export class BaseController {
* @param {Object} paginationObj
* @returns {void}
*/
private setPagination(paginationObj: object): void {
this.pagination = paginationObj;
private async setPagination(req, queryParams: any): Promise<void> {

let limit = queryParams !== undefined && queryParams.hasOwnProperty("limit") ? queryParams.limit : 100;
let count = queryParams !== undefined &&
queryParams.hasOwnProperty("count") ? queryParams.count : await this.modelObj.count();

limit = parseInt(limit, 10);
count = parseInt(count, 10);

const totalPages = limit > count ? 1 : (count / limit);
const currentPage = queryParams !== undefined && queryParams.hasOwnProperty("page") ? queryParams.page : 1;
const nextPage = currentPage === totalPages ? currentPage : (currentPage + 1);
const prevPage = currentPage === 1 ? 1 : (currentPage - 1);
let next, prev;
next = prev = req.path + "?";
next = `${next}page=${nextPage}&limit=${limit}&count=${count}&totalPages=${totalPages}`;
prev = `${prev}page=${prevPage}&limit=${limit}&count=${count}&totalPages=${totalPages}`;

const pagination = {
currentPage,
totalPages,
count,
limit,
next,
prev,
};

this.pagination = pagination;
}

/**
Expand Down Expand Up @@ -358,4 +369,18 @@ export class BaseController {
return [];
}
}

/**
* Returns the relationships to load.
*
* @private
* @returns {string[]}
*/
private getRelations(): string[] {
if (this.modelObj.hasOwnProperty("relations")) {
return this.modelObj.relations;
} else {
return [];
}
}
}
105 changes: 105 additions & 0 deletions src/server/controllers/StoryController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as express from "express";
import * as rc from "routing-controllers";
import { BaseController } from "./BaseController";
import { Story } from "../models/Story";

@rc.JsonController()
export class StoryController extends BaseController {

/** @inheritDoc */
public modelName = "Story";
/** @inheritDoc */
public modelObj = Story;

/**
* Get all Stories.
*
* @param {express.Request} req - The express request object.
* @param {express.Response} res - The express response object.
* @param {any} params - The query parameters.
* @returns {Promise<express.Response>} - An express Response object with the JSON response,
* wrapped in a Promise.
*/
@rc.Get("/stories")
public getAllStories(@rc.Req() req: express.Request,
@rc.Res() res: express.Response,
@rc.QueryParams() params: any)
: Promise<express.Response> {

return this.getAll(req, res, params);
}

/**
* Get Story by Id.
*
* @param {string} storyId - The Story Id.
* @param {express.Request} req - The express request object.
* @param {express.Response} res - The express response object.
* @returns {Promise<express.Response>} - An express Response object with the JSON response,
* wrapped in a Promise.
*/
@rc.Get("/story/:id")
public getOneStory(@rc.Param("id") id: string,
@rc.Req() req: express.Request,
@rc.Res() res: express.Response)
: Promise<express.Response> {

return this.getOne(id, req, res);
}

/**
* Create new Story.
*
* @param {Story} params - The Story properties required to create a new Story.
* @param {express.Request} req - The express request object.
* @param {express.Response} res - The express response object.
* @returns {Promise<express.Response>} - An express Response object with the JSON response,
* wrapped in a Promise.
*/
@rc.Post("/story")
public addStory(@rc.Body() params: Story,
@rc.Req() req: express.Request,
@rc.Res() res: express.Response)
: Promise<express.Response> {

return this.addEntity(params, req, res);
}

/**
* Update Story by Id.
*
* @param {string} id - The Story Id.
* @param {JSON} updatedStory - Updated Story Object.
* @param {express.Request} req - The express request object.
* @param {express.Response} res - The express response object.
* @returns {Promise<express.Response>} - An express Response object with the JSON response,
* wrapped in a Promise.
*/
@rc.Put("/story/:id")
public updateStory(@rc.Param("id") id: string,
@rc.Body() updatedStory: JSON,
@rc.Req() req: express.Request,
@rc.Res() res: express.Response)
: Promise<express.Response> {

return this.updateEntity(id, updatedStory, req, res);
}

/**
* Delete Story by Id.
*
* @param {string} id - The Story Id.
* @param {express.Request} req - The express request object.
* @param {express.Response} res - The express response object.
* @returns {Promise<express.Response>} - An express Response object with the JSON response,
* wrapped in a Promise.
*/
@rc.Delete("/story/:id")
public deleteStory(@rc.Param("id") id: string,
@rc.Req() req: express.Request,
@rc.Res() res: express.Response)
: Promise<express.Response> {

return this.deleteEntity(id, req, res);
}
}
71 changes: 71 additions & 0 deletions src/server/migrations/1519495907197-InitTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ export class InitTest1519495907197 implements MigrationInterface {
);
`);

// Name: story; Type: TABLE; Schema: public; Owner:
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "story" (
id character varying NOT NULL,
uuid character varying NOT NULL,
title character varying NOT NULL,
slug character varying NOT NULL,
"isFeatured" boolean DEFAULT false NOT NULL,
status character varying DEFAULT 'editing'::character varying NOT NULL,
language character varying(2) DEFAULT 'en'::character varying NOT NULL,
metas json DEFAULT '[]'::json NOT NULL,
"authorId" character varying NOT NULL,
"pulisherId" character varying,
text text NOT NULL,
"publishedAt" date,
"createdAt" timestamp without time zone DEFAULT now() NOT NULL,
"updatedAt" timestamp without time zone DEFAULT now() NOT NULL
);
`);

// Name: role_id_seq; Type: SEQUENCE; Schema: public; Owner:
await queryRunner.query(`
CREATE SEQUENCE IF NOT EXISTS role_id_seq
Expand All @@ -54,6 +74,11 @@ export class InitTest1519495907197 implements MigrationInterface {
ALTER TABLE ONLY role ALTER COLUMN id SET DEFAULT nextval('role_id_seq'::regclass);
`);

// // Name: story id; Type: ADD CONSTRAINT; Schema: public; Owner:
// await queryRunner.query(`
// ALTER TABLE ONLY story ADD CONSTRAINT story_pkey PRIMARY KEY (id);
// `);

// Data for Name: role; Type: TABLE DATA; Schema: public; Owner:
await queryRunner.query(`
INSERT INTO role (id, type, permissions, "updatedAt", "createdAt")
Expand Down Expand Up @@ -180,13 +205,59 @@ export class InitTest1519495907197 implements MigrationInterface {
1
);
`);

// Data for Name: story; Type: TABLE DATA; Schema: public; Owner:
await queryRunner.query(`
INSERT INTO "story" (
"id", "uuid", "title", "slug", "authorId", "text", "createdAt", "updatedAt"
)
VALUES (
'd47807d629a2f7e1826fce903bb0b18b',
'11a516c7-cc77-4136-92da-329adbe79ae8',
'This is a test title',
'this-is-a-test-title',
'38f1b33aba8d9bd3d5108aec661a72eb',
'### YO YO YO!! \n <p>Test html</p> \n [this is a link](http://google.com)',
'2018-03-07T04:52:22.314Z',
'2018-03-07T04:52:22.314Z'
);

INSERT INTO "story" (
"id", "uuid", "title", "slug", "authorId", "text", "createdAt", "updatedAt"
)
VALUES (
'e1b6b01d018ab1d8138bb3f8a70bf580',
'55bbd633-8149-44a9-80a2-32503be13bd1',
'This is a another test title',
'this-is-a-another-test-title',
'38f1b33aba8d9bd3d5108aec661a72eb',
'### HEY HEY HEY!! \n <div>Test html div tags</div> \n*[this is a emphasized link](http://google.com)*',
'2018-03-07T04:52:22.314Z',
'2018-03-07T04:52:22.314Z'
);

INSERT INTO "story" (
"id", "uuid", "title", "slug", "authorId", "text", "createdAt", "updatedAt"
)
VALUES (
'0160a258dd77e20e6c261cbd8bc4bbd1',
'c3b786e0-441d-422d-8e48-3b72c0f586d2',
'This is a another test title',
'this-is-a-another-test-title',
'38f1b33aba8d9bd3d5108aec661a72eb',
'# H1 header! \n <div>some sample html</div> \n*[some random emphasized link](http://google.com)*',
'2018-03-07T04:52:22.314Z',
'2018-03-07T04:52:22.314Z'
);
`);
}

public async down(queryRunner: QueryRunner): Promise<any> {
// Empty all tables
await queryRunner.query(`
delete from "user";
delete from "role";
delete from "story";
`);
}

Expand Down

0 comments on commit 63e24dc

Please sign in to comment.