diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b74a6b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=user-service-prod +DB_USER=user-service +DB_PASSWORD=user-service-password \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2c2a750..49f0fac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .env* output/ +.DS_Store .idea/ .nyc_output/ coverage/ diff --git a/Makefile b/Makefile index 1dde4fa..ed2a8b5 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ COMPOSE := docker-compose -f docker-compose.yml install: @echo "--- Installing dependencies." npm install + npm run build .PHONY: build build: diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/app/application.js b/app/application.js deleted file mode 100644 index a4c84b0..0000000 --- a/app/application.js +++ /dev/null @@ -1,19 +0,0 @@ -const express = require('express')(); -const bodyParser = require('body-parser'); - -const errorHandler = require('app/util/errorHandler'); -const ApplicationContext = require('app/applicationContext'); -const UserRouter = require('app/domain/user/userRouter'); -const GroupRouter = require('app/domain/group/groupRouter'); - -const applicationContext = new ApplicationContext(); - -const userRouter = new UserRouter(applicationContext.userValidation, applicationContext.userController); -const groupRouter = new GroupRouter(applicationContext.groupValidation, applicationContext.groupController); - -express.use(bodyParser.json()); -express.use('/users', userRouter); -express.use('/groups', groupRouter); -express.use(errorHandler); - -module.exports = express; diff --git a/app/application.ts b/app/application.ts new file mode 100644 index 0000000..0bd6889 --- /dev/null +++ b/app/application.ts @@ -0,0 +1,20 @@ +import UserRouter from "./domain/user/userRouter"; +import GroupRouter from "./domain/group/groupRouter"; +import errorHandler from "./util/errorHandler"; +import ApplicationContext from "./applicationContext"; + +const express = require('express')(); +const bodyParser = require('body-parser'); + +const applicationContext = new ApplicationContext(); + +const userRouter = new UserRouter(applicationContext.userValidation, applicationContext.userController); +const groupRouter = new GroupRouter(applicationContext.groupValidation, applicationContext.groupController); + +express.use(bodyParser.json()); +express.use('/users', userRouter); +express.use('/groups', groupRouter); +express.use(errorHandler); + +export default express; +module.exports = express; diff --git a/app/applicationContext.js b/app/applicationContext.ts similarity index 53% rename from app/applicationContext.js rename to app/applicationContext.ts index cfb1e2c..a75ec72 100644 --- a/app/applicationContext.js +++ b/app/applicationContext.ts @@ -1,16 +1,19 @@ -const knex = require('../database/knex'); - -const UserRepository = require('app/domain/user/userRepository'); -const UserService = require('app/domain/user/userService'); -const UserController = require('app/domain/user/userController'); -const UserValidation = require('app/domain/user/userValidation'); +import UserController from "./domain/user/userController"; +import UserValidation from "./domain/user/userValidation"; +import GroupController from "./domain/group/groupController"; +import GroupValidation from "./domain/group/groupValidation"; +import UserRepository from "./domain/user/userRepository"; +import UserService from "./domain/user/userService"; +import GroupRepository from "./domain/group/groupRepository"; +import GroupService from "./domain/group/groupService"; -const GroupRepository = require('app/domain/group/groupRepository'); -const GroupService = require('app/domain/group/groupService'); -const GroupController = require('app/domain/group/groupController'); -const GroupValidation = require('app/domain/group/groupValidation'); +const knex = require('../database/knex'); -class ApplicationContext { +export default class ApplicationContext { + public userController: UserController; + public userValidation: UserValidation; + public groupController: GroupController; + public groupValidation: GroupValidation; constructor() { const userRepository = new UserRepository(knex); diff --git a/app/domain/group/groupController.js b/app/domain/group/groupController.ts similarity index 61% rename from app/domain/group/groupController.js rename to app/domain/group/groupController.ts index 0cf5851..04390d8 100644 --- a/app/domain/group/groupController.js +++ b/app/domain/group/groupController.ts @@ -1,31 +1,33 @@ +import GroupService from "./groupService"; +import {Request, Response} from "express"; + const HttpStatus = require('http-status-codes'); -class GroupController { - constructor(groupService) { - this.groupService = groupService +export default class GroupController { + constructor(private groupService: GroupService) { } - async getAll(request, response) { + async getAll(request: Request, response: Response) { const users = await this.groupService.getAll(); response.status(HttpStatus.OK).json(users); } - async get(request, response) { + async get(request: Request, response: Response) { const user = await this.groupService.get(request.params.id); response.status(HttpStatus.OK).json(user); } - async create(request, response) { + async create(request: Request, response: Response) { const user = await this.groupService.create(request.body); response.status(HttpStatus.CREATED).json(user); } - async update(request, response) { + async update(request: Request, response: Response) { const user = await this.groupService.update(request.params.id, request.body); response.status(HttpStatus.OK).json(user); } - async delete(request, response) { + async delete(request: Request, response: Response) { await this.groupService.delete(request.params.id); response.sendStatus(HttpStatus.NO_CONTENT); } diff --git a/app/domain/group/groupRepository.js b/app/domain/group/groupRepository.ts similarity index 64% rename from app/domain/group/groupRepository.js rename to app/domain/group/groupRepository.ts index aadb62e..dc9c268 100644 --- a/app/domain/group/groupRepository.js +++ b/app/domain/group/groupRepository.ts @@ -1,29 +1,33 @@ +import Knex = require("knex"); + const GROUPS = 'Groups'; -const FIELD = ['id', 'name', 'admin']; +const FIELD = ['id', 'name', 'admin']; + +interface Group { +} -class GroupRepository { +export default class GroupRepository { - constructor(knex) { - this.knex = knex; + constructor(private knex: Knex) { } getAll() { return this.knex.select(FIELD).from(GROUPS); } - get(id) { + get(id: number) { return this.knex.first(FIELD).from(GROUPS).where({id}); } - create(group) { + create(group: Group) { return this.knex.insert(group, FIELD).into(GROUPS); } - update(id, group) { + update(id: number, group: Group) { return this.knex.update(group, FIELD).into(GROUPS).where({id}); } - delete(id) { + delete(id: number) { return this.knex.delete().from(GROUPS).where({id: +id}); } diff --git a/app/domain/group/groupRouter.js b/app/domain/group/groupRouter.js deleted file mode 100644 index 1e83846..0000000 --- a/app/domain/group/groupRouter.js +++ /dev/null @@ -1,28 +0,0 @@ -const wrapAsync = require('app/util/wrapAsync'); - -const express = require('express'); - -class GroupRouter extends express.Router { - - constructor(userValidation, userController) { - super(); - - //FIXME see baind - const createUserValidator = wrapAsync((request, response, next) => userValidation.createValidator(request, response, next)); - const updateUserValidator = wrapAsync((request, response, next) => userValidation.updateValidator(request, response, next)); - - const getUsers = wrapAsync((request, response) => userController.getAll(request, response)); - const getUser = wrapAsync((request, response) => userController.get(request, response)); - const createUser = wrapAsync((request, response) => userController.create(request, response)); - const updateUser = wrapAsync((request, response) => userController.update(request, response)); - const deleteUser = wrapAsync((request, response) => userController.delete(request, response)); - - this.get('/', getUsers); - this.get('/:id', getUser); - this.post('/', createUserValidator, createUser); - this.put('/:id', updateUserValidator, updateUser); - this.delete('/:id', deleteUser); - } -} - -module.exports = GroupRouter; \ No newline at end of file diff --git a/app/domain/group/groupRouter.ts b/app/domain/group/groupRouter.ts new file mode 100644 index 0000000..c74f9af --- /dev/null +++ b/app/domain/group/groupRouter.ts @@ -0,0 +1,32 @@ +import {NextFunction, Request, Response} from "express"; +import GroupValidation from "./groupValidation"; +import GroupController from "./groupController"; + +import wrapAsync from "../../util/wrapAsync"; + +const express = require('express'); + +export default class GroupRouter extends express.Router { + + constructor(userValidation: GroupValidation, userController: GroupController) { + super(); + + //FIXME see baind + const createUserValidator = wrapAsync((request: Request, response: Response, next: NextFunction) => userValidation.createValidator(request, response, next)); + const updateUserValidator = wrapAsync((request: Request, response: Response, next: NextFunction) => userValidation.updateValidator(request, response, next)); + + const getUsers = wrapAsync((request: Request, response: Response) => userController.getAll(request, response)); + const getUser = wrapAsync((request: Request, response: Response) => userController.get(request, response)); + const createUser = wrapAsync((request: Request, response: Response) => userController.create(request, response)); + const updateUser = wrapAsync((request: Request, response: Response) => userController.update(request, response)); + const deleteUser = wrapAsync((request: Request, response: Response) => userController.delete(request, response)); + + this.get('/', getUsers); + this.get('/:id', getUser); + this.post('/', createUserValidator, createUser); + this.put('/:id', updateUserValidator, updateUser); + this.delete('/:id', deleteUser); + } +} + +module.exports = GroupRouter; \ No newline at end of file diff --git a/app/domain/group/groupSchema.js b/app/domain/group/groupSchema.js deleted file mode 100644 index 1b43140..0000000 --- a/app/domain/group/groupSchema.js +++ /dev/null @@ -1,13 +0,0 @@ -const Joi = require('joi'); - -const update = create = Joi.object() -.options({abortEarly: false}) -.keys({ - name: Joi.string().required().min(3).label('Group name'), - admin: Joi.boolean().optional().label('Admin flag') -}).required().label('body'); - -module.exports = { - create, - update -}; \ No newline at end of file diff --git a/app/domain/group/groupSchema.ts b/app/domain/group/groupSchema.ts new file mode 100644 index 0000000..98dddd1 --- /dev/null +++ b/app/domain/group/groupSchema.ts @@ -0,0 +1,15 @@ +import Joi from "joi"; + +export const update = Joi.object() + .options({abortEarly: false}) + .keys({ + name: Joi.string().required().min(3).label('Group name'), + admin: Joi.boolean().optional().label('Admin flag') + }).required().label('body'); + +export const create = update; + +module.exports = { + create, + update +}; \ No newline at end of file diff --git a/app/domain/group/groupService.js b/app/domain/group/groupService.ts similarity index 71% rename from app/domain/group/groupService.js rename to app/domain/group/groupService.ts index 6619a0f..b2c315f 100644 --- a/app/domain/group/groupService.js +++ b/app/domain/group/groupService.ts @@ -1,16 +1,21 @@ -const NotFoundError = require('app/error/notFoundError'); +import GroupRepository from "./groupRepository"; -class GroupService { +import NotFoundError from "../../error/notFoundError"; - constructor(groupRepository) { - this.groupRepository = groupRepository; +interface GroupDto { + id: number; +} + +export default class GroupService { + + constructor(private groupRepository: GroupRepository) { } async getAll() { return this.groupRepository.getAll(); } - async get(id) { + async get(id: number) { const group = await this.groupRepository.get(id); if (typeof group === 'undefined') { throw new NotFoundError(`No group ${id}`); @@ -19,13 +24,13 @@ class GroupService { return group } - async create(group) { + async create(group: GroupDto) { // FIXME create new object delete group.id; return this.groupRepository.create(group); } - async update(id, group) { + async update(id: number, group: GroupDto) { const existingGroup = await this.groupRepository.get(id); if (typeof existingGroup === 'undefined') { throw new NotFoundError(`No group ${id}`); @@ -35,7 +40,7 @@ class GroupService { return this.groupRepository.update(id, group); } - async delete(id) { + async delete(id: number) { const existingGroup = await this.groupRepository.get(id); if (typeof existingGroup === 'undefined') { throw new NotFoundError(`No group ${id}`); diff --git a/app/domain/group/groupValidation.js b/app/domain/group/groupValidation.js deleted file mode 100644 index 3a61ea6..0000000 --- a/app/domain/group/groupValidation.js +++ /dev/null @@ -1,22 +0,0 @@ -const joiValidate = require('app/util/joiValidate'); - -const groupSchema = require('app/domain/group/groupSchema'); - -const BedRequestError = require('app/error/badRequestError'); - -class GroupValidation { - - async createValidator(request, response, next) { - await joiValidate(request.body, groupSchema.create, BedRequestError); - - next(); - } - - async updateValidator(request, response, next) { - await joiValidate(request.body, groupSchema.update, BedRequestError); - - next(); - } -} - -module.exports = GroupValidation; diff --git a/app/domain/group/groupValidation.ts b/app/domain/group/groupValidation.ts new file mode 100644 index 0000000..96b659e --- /dev/null +++ b/app/domain/group/groupValidation.ts @@ -0,0 +1,23 @@ +import {NextFunction, Request, Response} from "express"; +import {create, update} from "./groupSchema"; + +import joiValidate from "../../util/joiValidate"; + +import BedRequestError from "../../error/badRequestError"; + +export default class GroupValidation { + + async createValidator(request: Request, response: Response, next: NextFunction) { + await joiValidate(request.body, create, BedRequestError); + + next(); + } + + async updateValidator(request: Request, response: Response, next: NextFunction) { + await joiValidate(request.body, update, BedRequestError); + + next(); + } +} + +module.exports = GroupValidation; diff --git a/app/domain/user/userController.js b/app/domain/user/userController.ts similarity index 58% rename from app/domain/user/userController.js rename to app/domain/user/userController.ts index 202b542..73d73fe 100644 --- a/app/domain/user/userController.js +++ b/app/domain/user/userController.ts @@ -1,34 +1,38 @@ +import UserService from "./userService"; + const HttpStatus = require('http-status-codes'); -class UserController { - constructor(userService) { - this.userService = userService +import {Request, Response} from "express" + +export default class UserController { + + constructor(private userService: UserService) { } - async getAll(request, response) { + async getAll(request: Request, response: Response) { const users = await this.userService.getAll(); response.status(HttpStatus.OK).json(users); } - async get(request, response) { + async get(request: Request, response: Response) { const user = await this.userService.get(request.params.id); response.status(HttpStatus.OK).json(user); } - async create(request, response) { + async create(request: Request, response: Response) { const user = await this.userService.create(request.body); response.status(HttpStatus.CREATED).json(user); } - async update(request, response) { + async update(request: Request, response: Response) { const user = await this.userService.update(request.params.id, request.body); response.status(HttpStatus.OK).json(user); } - async delete(request, response) { + async delete(request: Request, response: Response) { await this.userService.delete(request.params.id); response.sendStatus(HttpStatus.NO_CONTENT); } } -module.exports = UserController; +module.exports = UserController; \ No newline at end of file diff --git a/app/domain/user/userRepository.js b/app/domain/user/userRepository.ts similarity index 69% rename from app/domain/user/userRepository.js rename to app/domain/user/userRepository.ts index 2a04474..bfe5c4e 100644 --- a/app/domain/user/userRepository.js +++ b/app/domain/user/userRepository.ts @@ -1,29 +1,33 @@ +import Knex = require("knex"); + const USERS = 'Users'; const FIELD = ['id', 'name', 'login', 'email']; -class UserRepository { +interface User { +} + +export default class UserRepository { - constructor(knex) { - this.knex = knex; + constructor(private knex: Knex) { } getAll() { return this.knex.select(FIELD).from(USERS); } - get(id) { + get(id: number) { return this.knex.first(FIELD).from(USERS).where({id}); } - create(user) { + create(user: User) { return this.knex.insert(user, FIELD).into(USERS); } - update(id, user) { + update(id: number, user: User) { return this.knex.update(user, FIELD).into(USERS).where({id}); } - delete(id) { + delete(id: number) { return this.knex.delete().from(USERS).where({id: +id}); } diff --git a/app/domain/user/userRouter.js b/app/domain/user/userRouter.js deleted file mode 100644 index 4ddd07f..0000000 --- a/app/domain/user/userRouter.js +++ /dev/null @@ -1,27 +0,0 @@ -const wrapAsync = require('app/util/wrapAsync'); - -const express = require('express'); - -class UserRouter extends express.Router { - - constructor(userValidation, userController) { - super(); - - const createUserValidator = wrapAsync((request, response, next) => userValidation.createValidator(request, response, next)); - const updateUserValidator = wrapAsync((request, response, next) => userValidation.updateValidator(request, response, next)); - - const getUsers = wrapAsync((request, response) => userController.getAll(request, response)); - const getUser = wrapAsync((request, response) => userController.get(request, response)); - const createUser = wrapAsync((request, response) => userController.create(request, response)); - const updateUser = wrapAsync((request, response) => userController.update(request, response)); - const deleteUser = wrapAsync((request, response) => userController.delete(request, response)); - - this.get('/', getUsers); - this.get('/:id', getUser); - this.post('/', createUserValidator, createUser); - this.put('/:id', updateUserValidator, updateUser); - this.delete('/:id', deleteUser); - } -} - -module.exports = UserRouter; diff --git a/app/domain/user/userRouter.ts b/app/domain/user/userRouter.ts new file mode 100644 index 0000000..1e7dcd6 --- /dev/null +++ b/app/domain/user/userRouter.ts @@ -0,0 +1,31 @@ +import UserValidation from "./userValidation"; +import UserController from "./userController"; + +const express = require('express'); +import {Request, Response, NextFunction} from "express" + +import wrapAsync from "../../util/wrapAsync"; + +export default class UserRouter extends express.Router { + + constructor(userValidation: UserValidation, userController: UserController) { + super(); + + const createUserValidator = wrapAsync((request: Request, response: Response, next: NextFunction) => userValidation.createValidator(request, response, next)); + const updateUserValidator = wrapAsync((request: Request, response: Response, next: NextFunction) => userValidation.updateValidator(request, response, next)); + + const getUsers = wrapAsync((request: Request, response: Response) => userController.getAll(request, response)); + const getUser = wrapAsync((request: Request, response: Response) => userController.get(request, response)); + const createUser = wrapAsync((request: Request, response: Response) => userController.create(request, response)); + const updateUser = wrapAsync((request: Request, response: Response) => userController.update(request, response)); + const deleteUser = wrapAsync((request: Request, response: Response) => userController.delete(request, response)); + + this.get('/', getUsers); + this.get('/:id', getUser); + this.post('/', createUserValidator, createUser); + this.put('/:id', updateUserValidator, updateUser); + this.delete('/:id', deleteUser); + } +} + +module.exports = UserRouter; diff --git a/app/domain/user/userSchema.js b/app/domain/user/userSchema.js deleted file mode 100644 index 9aaa033..0000000 --- a/app/domain/user/userSchema.js +++ /dev/null @@ -1,27 +0,0 @@ -const Joi = require('joi'); - -const create = Joi.object() -.options({abortEarly: false}) -.keys({ - name: Joi.string().required().min(3).label('User name'), - login: Joi.string().required().min(5).label('User login'), - email: Joi.string().email().required().label('User email'), - password: Joi.string().required().label('User password'), - confirmPassword: Joi.string().valid(Joi.ref('password')).required().options({language: {any: {allowOnly: 'must match password'}}}).label('User confirm password') -}).required().label('body'); - - -const update = Joi.object() -.options({abortEarly: false}) -.keys({ - name: Joi.string().required().min(3).label('User name'), - login: Joi.string().required().min(5).label('User login'), - email: Joi.string().email().required().label('User email'), - password: Joi.string().optional().label('User password'), - confirmPassword: Joi.string().optional().valid(Joi.ref('password')).optional().options({language: {any: {allowOnly: 'must match password'}}}).label('User confirm password') -}).required().label('body'); - -module.exports = { - create, - update -}; \ No newline at end of file diff --git a/app/domain/user/userSchema.ts b/app/domain/user/userSchema.ts new file mode 100644 index 0000000..01207d8 --- /dev/null +++ b/app/domain/user/userSchema.ts @@ -0,0 +1,27 @@ +import Joi from "joi"; + +export const create = Joi.object() + .options({abortEarly: false}) + .keys({ + name: Joi.string().required().min(3).label('User name'), + login: Joi.string().required().min(5).label('User login'), + email: Joi.string().email().required().label('User email'), + password: Joi.string().required().label('User password'), + confirmPassword: Joi.string().valid(Joi.ref('password')).required().options({language: {any: {allowOnly: 'must match password'}}}).label('User confirm password') + }).required().label('body'); + + +export const update = Joi.object() + .options({abortEarly: false}) + .keys({ + name: Joi.string().required().min(3).label('User name'), + login: Joi.string().required().min(5).label('User login'), + email: Joi.string().email().required().label('User email'), + password: Joi.string().optional().label('User password'), + confirmPassword: Joi.string().optional().valid(Joi.ref('password')).optional().options({language: {any: {allowOnly: 'must match password'}}}).label('User confirm password') + }).required().label('body'); + +module.exports = { + create, + update +}; \ No newline at end of file diff --git a/app/domain/user/userService.js b/app/domain/user/userService.ts similarity index 71% rename from app/domain/user/userService.js rename to app/domain/user/userService.ts index f691303..27ff5c4 100644 --- a/app/domain/user/userService.js +++ b/app/domain/user/userService.ts @@ -1,16 +1,21 @@ -const NotFoundError = require('app/error/notFoundError'); +import UserRepository from "./userRepository"; +import NotFoundError from "../../error/notFoundError"; -class UserService { +interface UserDto { + id: number; + confirmPassword: number; +} + +export default class UserService { - constructor(userRepository) { - this.userRepository = userRepository; + constructor(private userRepository: UserRepository) { } async getAll() { return this.userRepository.getAll(); } - async get(id) { + async get(id: number) { const user = await this.userRepository.get(id); if (typeof user === 'undefined') { throw new NotFoundError(`No user ${id}`); @@ -19,14 +24,14 @@ class UserService { return user } - async create(user) { + async create(user: UserDto) { // FIXME create new object delete user.id; delete user.confirmPassword; return this.userRepository.create(user); } - async update(id, user) { + async update(id: number, user: UserDto) { const existingUser = await this.userRepository.get(id); if (typeof existingUser === 'undefined') { throw new NotFoundError(`No user ${id}`); @@ -37,7 +42,7 @@ class UserService { return this.userRepository.update(id, user); } - async delete(id) { + async delete(id: number) { const existingUser = await this.userRepository.get(id); if (typeof existingUser === 'undefined') { throw new NotFoundError(`No user ${id}`); diff --git a/app/domain/user/userValidation.js b/app/domain/user/userValidation.js deleted file mode 100644 index 5319666..0000000 --- a/app/domain/user/userValidation.js +++ /dev/null @@ -1,22 +0,0 @@ -const joiValidate = require('app/util/joiValidate'); - -const userSchema = require('app/domain/user/userSchema'); - -const BedRequestError = require('app/error/badRequestError'); - -class UserValidation { - - async createValidator(request, response, next) { - await joiValidate(request.body, userSchema.create, BedRequestError); - - next(); - } - - async updateValidator(request, response, next) { - await joiValidate(request.body, userSchema.update, BedRequestError); - - next(); - } -} - -module.exports = UserValidation; diff --git a/app/domain/user/userValidation.ts b/app/domain/user/userValidation.ts new file mode 100644 index 0000000..ead5144 --- /dev/null +++ b/app/domain/user/userValidation.ts @@ -0,0 +1,21 @@ +import {NextFunction, Request, Response} from "express"; +import {create, update} from "./userSchema"; +import joiValidate from "../../util/joiValidate"; +import BedRequestError from "../../error/badRequestError"; + +export default class UserValidation { + + async createValidator(request: Request, response: Response, next: NextFunction) { + await joiValidate(request.body, create, BedRequestError); + + next(); + } + + async updateValidator(request: Request, response: Response, next: NextFunction) { + await joiValidate(request.body, update, BedRequestError); + + next(); + } +} + +module.exports = UserValidation; \ No newline at end of file diff --git a/app/error/badRequestError.js b/app/error/badRequestError.ts similarity index 58% rename from app/error/badRequestError.js rename to app/error/badRequestError.ts index c556010..d59dd21 100644 --- a/app/error/badRequestError.js +++ b/app/error/badRequestError.ts @@ -1,8 +1,9 @@ const HttpStatus = require('http-status-codes'); -class BadRequestError extends Error { +export default class BadRequestError extends Error { + public status: number; - constructor(message) { + constructor(message: string) { super(message); this.status = HttpStatus.BAD_REQUEST; } diff --git a/app/error/notFoundError.js b/app/error/notFoundError.ts similarity index 58% rename from app/error/notFoundError.js rename to app/error/notFoundError.ts index 40de9c7..439e264 100644 --- a/app/error/notFoundError.js +++ b/app/error/notFoundError.ts @@ -1,8 +1,9 @@ const HttpStatus = require('http-status-codes'); -class NotFoundError extends Error { +export default class NotFoundError extends Error { + public status: number; - constructor(message) { + constructor(message:string) { super(message); this.status = HttpStatus.NOT_FOUND; } diff --git a/app/runner.js b/app/runner.ts similarity index 100% rename from app/runner.js rename to app/runner.ts diff --git a/app/setting.js b/app/setting.ts similarity index 86% rename from app/setting.js rename to app/setting.ts index b539ebd..3041be4 100644 --- a/app/setting.js +++ b/app/setting.ts @@ -3,7 +3,7 @@ const env = process.env.NODE_ENV || 'development'; require('dotenv').load({path: `.env.${env}`}); // FIXME see config -module.exports = { +const setting = { ENVIRONMENT: env, PORT: process.env.PORT || 3000, @@ -15,3 +15,6 @@ module.exports = { DB_NAME: process.env.DB_NAME || 'user-service', }; + +export default setting; +module.exports = setting; \ No newline at end of file diff --git a/app/util/errorHandler.js b/app/util/errorHandler.js deleted file mode 100644 index 10a0548..0000000 --- a/app/util/errorHandler.js +++ /dev/null @@ -1,20 +0,0 @@ -const HttpStatus = require('http-status-codes'); - -const BedRequestError = require('app/error/badRequestError'); -const NotFoundError = require('app/error/notFoundError'); - -module.exports = function(error, request, response, next) { - if ( - (error instanceof BedRequestError) || - (error instanceof NotFoundError) - ) { - response.status(error.status).json({error: error.message}); - } else if ( - (error instanceof Error) && - (typeof error.constraint !== 'undefined') - ) { - response.status(HttpStatus.CONFLICT).json({error: error.detail}); - } else { - next(); - } -}; diff --git a/app/util/errorHandler.ts b/app/util/errorHandler.ts new file mode 100644 index 0000000..0c1951f --- /dev/null +++ b/app/util/errorHandler.ts @@ -0,0 +1,27 @@ +import NotFoundError from "../error/notFoundError"; +import BedRequestError from "../error/badRequestError"; +import {NextFunction, Request, Response} from "express"; + +const HttpStatus = require('http-status-codes'); + +const errorHandler = function (error: Error, request: Request, response: Response, next: NextFunction) { + if ( + (error instanceof BedRequestError) || + (error instanceof NotFoundError) + ) { + response.status(error.status).json({error: error.message}); + } else if ( + (error instanceof Error) && + ( + // @ts-ignore + typeof error.constraint !== 'undefined') + ) { + // @ts-ignore + response.status(HttpStatus.CONFLICT).json({error: error.detail}); + } else { + next(); + } +}; + +export default errorHandler; +module.exports = errorHandler; \ No newline at end of file diff --git a/app/util/joiValidate.js b/app/util/joiValidate.ts similarity index 55% rename from app/util/joiValidate.js rename to app/util/joiValidate.ts index 8d44a5a..0291010 100644 --- a/app/util/joiValidate.js +++ b/app/util/joiValidate.ts @@ -1,6 +1,8 @@ +import {SchemaLike} from "joi"; + const Joi = require('joi'); -module.exports = function(body, schema, Error) { +const joiValidate = function (body: any, schema: SchemaLike, Error: any) { const {error, value} = Joi.validate(body, schema); if (error) { @@ -8,4 +10,6 @@ module.exports = function(body, schema, Error) { throw new Error(message); } return value; -}; \ No newline at end of file +}; +export default joiValidate; +module.exports = joiValidate; \ No newline at end of file diff --git a/app/util/wrapAsync.js b/app/util/wrapAsync.js deleted file mode 100644 index 1e33ee2..0000000 --- a/app/util/wrapAsync.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = (fn) => { - return (request, response, next) => { - // Make sure to `.catch()` any errors and pass them along to the `next()` - // middleware in the chain, in this case the error handler. - fn(request, response, next) - .catch(next); - }; -}; diff --git a/app/util/wrapAsync.ts b/app/util/wrapAsync.ts new file mode 100644 index 0000000..a0eba8f --- /dev/null +++ b/app/util/wrapAsync.ts @@ -0,0 +1,13 @@ +import {NextFunction, Request, Response} from "express"; + +const wrap = (fn: any) => { + return (request: Request, response: Response, next: NextFunction) => { + // Make sure to `.catch()` any errors and pass them along to the `next()` + // middleware in the chain, in this case the error handler. + fn(request, response, next) + .catch(next); + }; +}; + +export default wrap; +module.exports = wrap; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 732cbd8..d9dcac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,6 +156,103 @@ "integrity": "sha512-D7VxhADdZbDJ0HjUTMnSQ5xIGb4H2yWpg8k9Sf1T08zfFiQYlaxM8LZydpR4FQ2E6LZJX8IlabNZ5io4vdChwg==", "dev": true }, + "@types/bluebird": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.24.tgz", + "integrity": "sha512-YeQoDpq4Lm8ppSBqAnAeF/xy1cYp/dMTif2JFcvmAbETMRlvKHT2iLcWu+WyYiJO3b3Ivokwo7EQca/xfLVJmg==", + "dev": true + }, + "@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "requires": { + "@types/connect": "3.4.32", + "@types/node": "10.11.4" + } + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "10.11.4" + } + }, + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "@types/express": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", + "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", + "dev": true, + "requires": { + "@types/body-parser": "1.17.0", + "@types/express-serve-static-core": "4.16.0", + "@types/serve-static": "1.13.2" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz", + "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", + "dev": true, + "requires": { + "@types/events": "1.2.0", + "@types/node": "10.11.4", + "@types/range-parser": "1.2.2" + } + }, + "@types/joi": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@types/joi/-/joi-13.6.0.tgz", + "integrity": "sha512-vgxADik+Va7AcMZ89C7MVod3DlB3SWcj4dDI+XIceObGFfscehtjA0fFzBCZLoRr7ShjLVHNu1gMk+HtrG8WeQ==", + "dev": true + }, + "@types/knex": { + "version": "0.14.26", + "resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.14.26.tgz", + "integrity": "sha512-BhtEH5PbB2kxuAwZ1w+KDNyL5NZXjTfoT6JtwnD3P/l3bqJJISCOAfPeH8O2aY66auscNIaCvIij/eONvlM4Cw==", + "dev": true, + "requires": { + "@types/bluebird": "3.5.24", + "@types/node": "10.11.4" + } + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", + "dev": true + }, + "@types/node": { + "version": "10.11.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.4.tgz", + "integrity": "sha512-ojnbBiKkZFYRfQpmtnnWTMw+rzGp/JiystjluW9jgN3VzRwilXddJ6aGQ9V/7iuDG06SBgn7ozW9k3zcAnYjYQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz", + "integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "4.16.0", + "@types/mime": "2.0.0" + } + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -4890,6 +4987,12 @@ "mime-types": "2.1.20" } }, + "typescript": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.1.tgz", + "integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==", + "dev": true + }, "uglify-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.0.tgz", diff --git a/package.json b/package.json index 249bd7f..b365350 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "user-service", "version": "1.0.0", - "description": "Play with passportjs", + "description": "User service", "main": "server.js", "scripts": { - "start": "node $NODE_DEBUG_OPTION server.js", + "build": "npm run build-ts", + "build-ts": "tsc -p .", + "start": "npm run build && node $NODE_DEBUG_OPTION server.js", "run": "node $NODE_DEBUG_OPTION runner.js", "test": "NODE_ENV=test knex migrate:latest && NODE_ENV=test mocha", "e2e": "node e2e-test/e2e.js", @@ -15,16 +17,16 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/LvivNodeJsClub/passport.git" + "url": "git+https://github.com/LvivNodeJsClub/user-service.git" }, "keywords": [ - "passport", + "user", "node" ], "bugs": { - "url": "https://github.com/LvivNodeJsClub/passport/issues" + "url": "https://github.com/LvivNodeJsClub/user-service/issues" }, - "homepage": "https://github.com/LvivNodeJsClub/passport#readme", + "homepage": "https://github.com/LvivNodeJsClub/user-service#readme", "dependencies": { "body-parser": "^1.18.3", "dotenv": "^6.0.0", @@ -38,6 +40,9 @@ "rootpath": "^0.1.2" }, "devDependencies": { + "@types/express": "^4.16.0", + "@types/joi": "^13.6.0", + "@types/knex": "^0.14.26", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "http-status-codes": "^1.3.0", @@ -46,7 +51,8 @@ "newman-reporter-html": "^1.0.2", "nyc": "^13.0.1", "sinon": "^6.1.5", - "supertest": "^3.1.0" + "supertest": "^3.1.0", + "typescript": "^3.1.1" }, "nyc": { "report-dir": "./output/nyc-report" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8acdd47 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,62 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + }, + "include": [ + "./app/**/*" + ] +}