Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement basic user component #5

Merged
merged 2 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ os:
- linux
- osx

before_script: ./bin/travis.sh
script: npm run test:ci
env: TASK=test
install: npm install --ignore-scripts
install: npm install
services: mongodb

matrix:
include:
Expand Down
3 changes: 3 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ environment:
- nodejs_version: '8'
- nodejs_version: '10'

services:
- mongodb

install:
- ps: Install-Product node $env:nodejs_version
- node --version
Expand Down
6 changes: 6 additions & 0 deletions bin/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

CONTAINER_NAME="mongodb_c"
docker rm -f $CONTAINER_NAME
docker pull mongo:latest
docker run --name $CONTAINER_NAME -p 27017:27017 -d mongo:latest
15 changes: 15 additions & 0 deletions bin/travis.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
set -e

if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
echo "OSX => UPDATING HOMEBREW"
brew update
echo "OSX => INSTALLING AND STARTING MONGODB"
brew install mongodb
brew services start mongodb
fi

if [ $TASK = "test" ]; then
echo "TASK => MONGODB STARTUP DELAY"
sleep 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a check for mongodb service / process here instead of using sleep?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would have to check and sleep / something if the service isn't up and ready yet anyways. This is the fix recommended by Travis themselves, documented here: https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections

fi
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
"clean": "lb-clean dist*",
"commit": "git-cz",
"commitmsg": "commitlint -E GIT_PARAMS",
"docker": "./bin/setup.sh",
"lint": "npm run prettier:check && npm run tslint",
"lint:fix": "npm run prettier:fix && npm run tslint:fix",
"prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"",
"prettier:check": "npm run prettier:cli -- -l",
"prettier:fix": "npm run prettier:cli -- --write",
"tslint": "lb-tslint",
"tslint:fix": "npm run tslint -- --fix",
"pretest": "npm run clean && npm run build",
"pretest": "npm run clean && npm run build && npm run docker",
"pretest:ci": "npm run build",
"test": "lb-mocha --allow-console-logs \"DIST/test\"",
"test:ci": "lb-mocha --allow-console-logs \"DIST/test\"",
Expand All @@ -50,20 +51,24 @@
"src"
],
"dependencies": {
"@loopback/context": "^0.12.2",
"@loopback/boot": "^0.12.2",
"@loopback/context": "^0.12.2",
"@loopback/core": "^0.11.2",
"@loopback/dist-util": "^0.3.5",
"@loopback/openapi-v3": "^0.12.2",
"@loopback/repository": "^0.14.2",
"@loopback/rest": "^0.19.2",
"@loopback/openapi-v3": "^0.12.2"
"bcryptjs": "^2.4.3",
"isemail": "^3.1.3",
"loopback-connector-mongodb": "^1.18.1"
},
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"@commitlint/travis-cli": "^7.0.0",
"@loopback/build": "^0.6.13",
"@loopback/testlab": "^0.11.2",
"@types/bcryptjs": "^2.4.1",
"@types/mocha": "^5.0.0",
"@types/node": "^10.1.1",
"commitizen": "^2.10.1",
Expand Down
1 change: 1 addition & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
// License text available at https://opensource.org/licenses/MIT

export * from './ping.controller';
export * from './user.controller';
51 changes: 51 additions & 0 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {repository} from '@loopback/repository';
import {post, param, get, requestBody, HttpErrors} from '@loopback/rest';
import {User} from '../models';
import {UserRepository} from '../repositories';
import {hash} from 'bcryptjs';
import {promisify} from 'util';
import * as isemail from 'isemail';

const hashAsync = promisify(hash);

export class UserController {
constructor(
@repository(UserRepository) public userRepository: UserRepository,
) {}

@post('/users')
async create(@requestBody() user: User): Promise<User> {
// Validate Email
if (!isemail.validate(user.email)) {
throw new HttpErrors.UnprocessableEntity('invalid email');
}

// Validate Password Length
if (user.password.length < 8) {
throw new HttpErrors.UnprocessableEntity(
'password must be minimum 8 characters',
);
}

// Salt + Hash Password
user.password = await hashAsync(user.password, 10);

// Save & Return Result
const savedUser = await this.userRepository.create(user);
delete savedUser.password;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is good enough for the initial implementation, it is also very brittle. We should find a more robust way how to allow models to hide certain properties from toJSON output.

In the past, I had very good experience with moving the password to a different model (table/collection) and use hasMany relation. As a nice side effect, by keeping a hash of all previous passwords, we can easily implement a password policy like "cannot reuse the same password".

return savedUser;
}

@get('/users/{id}')
async findById(@param.path.string('id') id: string): Promise<User> {
const user = await this.userRepository.findById(id, {
fields: {password: false},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

});
return user;
}
}
6 changes: 6 additions & 0 deletions src/datasources/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './user.datasource';
10 changes: 10 additions & 0 deletions src/datasources/user.datasource.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "user",
"connector": "mongodb",
"url": "",
"host": "127.0.0.1",
"port": 27017,
"user": "",
"password": "",
"database": ""
}
19 changes: 19 additions & 0 deletions src/datasources/user.datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {inject} from '@loopback/core';
import {juggler, AnyObject} from '@loopback/repository';
const config = require('./user.datasource.json');

export class UserDataSource extends juggler.DataSource {
static dataSourceName = 'user';

constructor(
@inject('datasources.config.user', {optional: true})
dsConfig: AnyObject = config,
) {
super(dsConfig);
}
}
6 changes: 6 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './user.model';
41 changes: 41 additions & 0 deletions src/models/user.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Entity, model, property} from '@loopback/repository';

@model()
export class User extends Entity {
@property({
type: 'string',
id: true,
})
id: string;

@property({
type: 'string',
required: true,
})
email: string;

@property({
type: 'string',
required: true,
})
password: string;

@property({
type: 'string',
})
firstname?: string;

@property({
type: 'string',
})
surname?: string;

constructor(data?: Partial<User>) {
super(data);
}
}
6 changes: 6 additions & 0 deletions src/repositories/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './user.repository';
19 changes: 19 additions & 0 deletions src/repositories/user.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/example-shopping
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {DefaultCrudRepository, juggler} from '@loopback/repository';
import {User} from '../models';
import {inject} from '@loopback/core';

export class UserRepository extends DefaultCrudRepository<
User,
typeof User.prototype.id
> {
constructor(
@inject('datasources.user') protected datasource: juggler.DataSource,
) {
super(User, datasource);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {createClientForHandler, supertest} from '@loopback/testlab';
import {RestServer} from '@loopback/rest';
import {ShoppingApplication} from '../';
import {ShoppingApplication} from '../..';
Copy link

@shimks shimks Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably a good reminder for us to change the template generated by our app generator so that this file lives in the acceptance folder

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shimks @virkt25 could you please create a follow up issue or open a pull request to fix the template? Otherwise we are most likely going to forget about this.


describe('PingController', () => {
let app: ShoppingApplication;
Expand Down
Loading