Skip to content

Commit ad5a45b

Browse files
committed
initial source
0 parents  commit ad5a45b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1392
-0
lines changed

.env.example

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SALT_ROUNDS=11
2+
ACCESS_TOKEN_DURATION=24h
3+
REFRESH_TOKEN_DURATION=1h
4+
ACCESS_TOKEN_SECRET_KEY=bAKVdqczerYAYKdMxsaBzbFUJU6ZvL2LwZuxhxyS
5+
REFRESH_TOKEN_SECRET_KEY=bAKVdqczerYAYKdMxsaBzbFUJU6ZvL2LwZuxhtpS

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
build
3+
public
4+
yarn.lock
5+
package-lock.json
6+
.env

.sequelizerc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const path = require('path')
2+
3+
module.exports = {
4+
"config": path.resolve('./src/sqlz/config', 'config.json'),
5+
"models-path": path.resolve('./src/sqlz/models'),
6+
"seeders-path": path.resolve('./src/sqlz/seeders'),
7+
"migrations-path": path.resolve('./src/sqlz/migrations')
8+
}

package.json

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"name": "post-rate",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node build/index.js",
8+
"predev": "npm run swagger",
9+
"prebuild": "npm run swagger",
10+
"build": "tsc",
11+
"dev": "cross-env NODE_ENV=development && concurrently \"nodemon\" \"nodemon -x tsoa spec\"",
12+
"swagger": "tsoa spec",
13+
"test": "jest",
14+
"migrate": "./node_modules/.bin/sequelize db:migrate",
15+
"create:db": "./node_modules/.bin/sequelize db:create",
16+
"make:migration": "./node_modules/.bin/sequelize migration:generate --name",
17+
"undo:migrate": "./node_modules/.bin/sequelize db:migrate:undo",
18+
"undo:migrate:all": "./node_modules/.bin/sequelize db:migrate:undo:all",
19+
"seed": "./node_modules/.bin/sequelize db:seed:all",
20+
"make:seeder": "./node_modules/.bin/sequelize seed:generate --name",
21+
"undo:seeder": "./node_modules/.bin/sequelize db:seed:undo",
22+
"undo:seeder:all": "./node_modules/.bin/sequelize db:seed:undo:all",
23+
"lint:init": "tslint --init",
24+
"lint": "tslint --project tsconfig.json",
25+
"lint:fix": "tslint --project tsconfig.json --fix"
26+
},
27+
"author": "",
28+
"license": "ISC",
29+
"devDependencies": {
30+
"@types/bcrypt": "^3.0.0",
31+
"@types/cors": "^2.8.10",
32+
"@types/express": "^4.17.8",
33+
"@types/express-boom": "^3.0.0",
34+
"@types/faker": "^5.1.5",
35+
"@types/jsonwebtoken": "^8.5.0",
36+
"@types/morgan": "^1.9.1",
37+
"@types/node": "^14.17.7",
38+
"@types/swagger-ui-express": "^4.1.2",
39+
"@types/validator": "^13.6.3",
40+
"concurrently": "^5.3.0",
41+
"faker": "^5.1.0",
42+
"jest": "^26.6.3",
43+
"sequelize-cli": "^6.2.0",
44+
"ts-jest": "^26.4.4",
45+
"ts-node": "^10.1.0",
46+
"tslint": "^6.1.3",
47+
"typescript": "^4.0.3"
48+
},
49+
"dependencies": {
50+
"bcrypt": "^5.0.1",
51+
"body-parser": "^1.19.0",
52+
"cors": "^2.8.5",
53+
"cross-env": "^7.0.3",
54+
"depcheck": "^1.4.1",
55+
"dotenv": "^10.0.0",
56+
"express": "^4.17.1",
57+
"express-boom": "^3.0.0",
58+
"http-status-codes": "^2.1.4",
59+
"jsonwebtoken": "^8.5.1",
60+
"morgan": "^1.10.0",
61+
"pg": "^8.5.1",
62+
"reflect-metadata": "^0.1.13",
63+
"sequelize": "^6.5.0",
64+
"swagger-ui-express": "^4.1.4",
65+
"tsoa": "^3.8.0"
66+
},
67+
"nodemonConfig": {
68+
"watch": [
69+
"src"
70+
],
71+
"ext": "ts",
72+
"exec": "ts-node src/index.ts"
73+
}
74+
}

src/config.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as dotenv from 'dotenv';
2+
dotenv.config();
3+
4+
export const config = {
5+
saltRounds: Number(process.env.SALT_ROUNDS) || 11,
6+
accessTokenDuration: process.env.ACCESS_TOKEN_DURATION || '24h',
7+
refreshTokenDuration: process.env.REFRESH_TOKEN_DURATION || '1h',
8+
accessTokenSecretKey:
9+
process.env.ACCESS_TOKEN_SECRET_KEY || '<ACCESS_TOKEN_SECRET_KEY>',
10+
refreshTokenSecretKey:
11+
process.env.REFRESH_TOKEN_SECRET_KEY || '<REFRESH_TOKEN_SECRET_KEY>'
12+
13+
};

src/constants/ErrorType.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* List of ErrorType.
3+
*/
4+
enum ErrorType {
5+
INVALID = 'JsonWebTokenError',
6+
EXPIRED = 'TokenExpiredError',
7+
NO_ROWS_UPDATED_ERROR = 'No Rows Updated'
8+
}
9+
10+
export default ErrorType;

src/constants/tables.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface ITable {
2+
[key: string]: string
3+
}
4+
export const TABLES: ITable = {
5+
USER_SESSION: 'userSession',
6+
USER: 'user',
7+
POST: 'post',
8+
POST_RATE: 'postRate',
9+
};
10+
11+
export default TABLES;

src/controllers/post.controller.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { getModelFromCollectionBase } from '../utils';
2+
import {
3+
Route,
4+
Tags,
5+
Post,
6+
Body,
7+
Path,
8+
Put,
9+
Delete,
10+
Patch,
11+
Get,
12+
} from 'tsoa';
13+
import {
14+
IPostPayload,
15+
IResponse,
16+
} from '../interfaces';
17+
import { Post as PostModel, PostRate } from '../sqlz/models';
18+
19+
@Route('api/v1/post')
20+
@Tags('Post')
21+
export default class UserAuthController {
22+
23+
@Get('/:id')
24+
public async fetch(@Path() id: number) {
25+
try {
26+
let post = await PostModel.findOne({ where: { id }, include: [{ model: PostRate, as: 'rates', required: false }] });
27+
if (post) {
28+
post = getModelFromCollectionBase(post);
29+
const p: any = {...post};
30+
const {rates} = p;
31+
let avgRate = 0;
32+
if (rates && rates.length > 0) {
33+
const rateLen = rates.length;
34+
rates.map((r: any) => {
35+
avgRate = avgRate + (r.rate / rateLen);
36+
});
37+
}
38+
return { status: 200, data: {...post, avgRate}, error: null, message: 'Success to fetch post!' };
39+
}
40+
return { status: 400, data: null, error: null, message: 'Success to fetch post!' };
41+
} catch (error) {
42+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to fetch post!' };
43+
return response
44+
}
45+
}
46+
47+
@Post('/')
48+
public async create(@Body() body: IPostPayload) {
49+
try {
50+
const post = await PostModel.create({ ...body });
51+
if (post) {
52+
return { status: 200, data: post, error: null, message: 'Success to create post!' };
53+
}
54+
const response: IResponse = { status: 400, data: null, error: null, message: 'Failed to create post!' };
55+
return response;
56+
} catch (error) {
57+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to create post!' };
58+
return response;
59+
}
60+
}
61+
62+
@Patch('/:id')
63+
public async update(@Body() body: IPostPayload, @Path() id: number) {
64+
try {
65+
const exPost = await PostModel.findByPk(id);
66+
if (exPost) {
67+
const post = await PostModel.update({ ...body }, { where: { id }, returning: true });
68+
return { status: 200, data: post[1][0], error: null, message: 'Success to update post!' };
69+
}
70+
const response: IResponse = { status: 400, data: null, error: null, message: 'Failed to update post!' };
71+
return response
72+
} catch (error) {
73+
const response: IResponse = { status: 400, data: null, error, message: 'Failed o update post!' };
74+
return response;
75+
}
76+
}
77+
78+
@Put('/:postId/:userId/:rate')
79+
public async rate(@Path() postId: number, @Path() userId: number, @Path() rate: number) {
80+
try {
81+
const exPostRate = await PostRate.findOne({ where: { postId, userId } });
82+
if (exPostRate) {
83+
await PostRate.destroy({ where: { postId, userId } });
84+
return { status: 200, data: null, error: null, message: 'Success to remove ost\'s rate!' };
85+
}
86+
const postRate = await PostRate.create({ postId, userId, rate });
87+
if (postRate) {
88+
return { status: 200, data: postRate, error: null, message: 'Success o rate post!' };
89+
}
90+
const response: IResponse = { status: 400, data: null, error: null, message: 'Faile to rate post!' };
91+
return response
92+
}catch (error) {
93+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to create post!' };
94+
return response
95+
}
96+
}
97+
98+
@Delete('/:postId')
99+
public async delete(@Path() postId: number) {
100+
try {
101+
await PostRate.destroy({ where: { postId } });
102+
await PostModel.destroy({ where: { id: postId } });
103+
const response: IResponse = { status: 200, data: null, error: null, message: 'Success to remove posts!' };
104+
return response;
105+
} catch (error) {
106+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to remo;ve post!' };
107+
return response
108+
}
109+
}
110+
}
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {
2+
Route,
3+
Tags,
4+
Post,
5+
Body,
6+
} from 'tsoa';
7+
import { User } from '../sqlz/models';
8+
import {
9+
ILoginPayload,
10+
IResponse,
11+
IUserBasicPayload,
12+
ILogout
13+
} from '../interfaces';
14+
import * as UserSessionController from './userSession.controller';
15+
import { getModelFromCollectionBase, bcrypt, jwt } from '../utils';
16+
17+
@Route('api/v1/auth/user')
18+
@Tags('User Auth')
19+
export default class UserAuthController {
20+
21+
@Post('/signin')
22+
public async signIn(@Body() auth: ILoginPayload) {
23+
try {
24+
let result: any = await User.findOne({ where: { email: auth.email } });
25+
result = getModelFromCollectionBase(result);
26+
if (result) {
27+
const isSame = await bcrypt.compare(auth.password, result.password);
28+
if (isSame) {
29+
const { id, email, firstname, lastname } = result;
30+
const loggedInPayload = {
31+
id,
32+
email,
33+
firstname,
34+
lastname,
35+
};
36+
const refreshToken = jwt.generateRefreshToken({ ...loggedInPayload });
37+
const userSessionPayload = {
38+
userId: id,
39+
token: refreshToken
40+
};
41+
const session = await UserSessionController.create(userSessionPayload);
42+
const accessToken = jwt.generateAccessToken({
43+
...loggedInPayload,
44+
sessionId: session.id
45+
});
46+
const response: IResponse = { status: 200, data: { accessToken, refreshToken }, error: null, message: 'Successed to signin!' };
47+
return response;
48+
} else {
49+
const response: IResponse = { status: 400, data: null, error: null, message: 'Password is wrong!' };
50+
return response;
51+
}
52+
} else {
53+
const response: IResponse = { status: 400, data: null, error: null, message: 'You are not registered!' };
54+
return response;
55+
}
56+
} catch (error) {
57+
return { status: 400, data: null, error, message: 'Failed to signin!' };
58+
}
59+
}
60+
61+
@Post('/signup')
62+
public async signUp(@Body() auth: IUserBasicPayload) {
63+
try {
64+
const result: any = await User.findOne({ where: { email: auth.email } });
65+
if (result) {
66+
return { status: 400, data: null, error: null, message: 'Please use other email.' };
67+
}
68+
const pwd = await bcrypt.hash(auth.password);
69+
const user: any = await User.create({ ...auth, password: pwd });
70+
const response: IResponse = { status: 200, data: user, error: null, message: 'Successed to register.' };
71+
return response;
72+
} catch (error) {
73+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to signup!' };
74+
return response;
75+
}
76+
}
77+
78+
@Post('/signout')
79+
public async signOut(@Body() body: ILogout) {
80+
try {
81+
const session = await UserSessionController.get(body.token);
82+
if (session) {
83+
await UserSessionController.remove(body.token);
84+
return { status: 200, data: null, error: null, message: 'Success to logout!' };
85+
}
86+
const response: IResponse = { status: 400, data: null, error: null, message: 'Failed to logout!' };
87+
return response;
88+
} catch (error) {
89+
const response: IResponse = { status: 400, data: null, error, message: 'Failed to logout!' };
90+
return response;
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)