Git Product home page Git Product logo

exp-starter-api's Introduction

CircleCI Code Climate Test Coverage Issue Count

README

steeeel it

  • clone it
  • reset your origin url to a new GH url that you own
  • add the repo on your code climate and circle CI account
  • change the urls of all the above badges to reflect your repositories
  • push up the repo and watch for circle and code climate to update
  • do your thing

contribute to it

  • fork, clone, setup locally following the 'set it up' instructions
  • add remote "upstream" with this repo's ssh url
  • checkout a branch and commit your work
  • push branch to your repo
  • submit pr
  • periodically pull upstream master into master, and rebase the branch on top, force pushing the rebased branch when necessary

todo

  • prettier
  • signup
  • testing for signup fails - user must have unique email
  • login
  • testing for login fails
  • users index - only users
  • testing for malformed JWT
  • users show - only users
  • users update - only self
  • users update feature test - get validation error
  • model test - user cannot update email to a pre-existing email address
  • model test - user cannot update email to a pre-existing email address regardless of case
  • model test - user cannot update email to a pre-existing email address regardless of spaces on either end of entry
  • migration - email must be unique on db
  • update to Node 8xx I guess ๐Ÿ™„
  • object creation methods? createUser() or createUser(overrides)
  • createUser() creates "unique" users each time with Math.rand or whatevs 4 random numbers? at the end of everything
  • import statements instead of requires where possible?
  • model test - findBy other than email (everything on user)
  • add circle, code climate
  • make code climate happy with trailing commas, eslint or something
  • prettier linter runs with test, throws and describes issue found
  • PR template

set it up

  1. $ yarn install
  2. $ cp .env.example .env
  3. $ createdb exp_starter_app_test
  4. $ createdb exp_starter_app_development
  5. $ yarn db:migrate
  6. $ yarn db:migrate:test
  7. rollback to a specific version:
    • $ MIGRATE_TO=<TIMESTAMP OF MIGRATION> yarn db:migrate
  8. $ nodemon start
    • $ yarn global add nodemon if you don't have it... this will restart your server on most changes

Tests, test coverage & reports, and linter

Tests (also runs linter on success)

  • $ yarn test

Test coverage and reports

  • $ yarn coverage - runs tests and reports coverage
  • $ yarn reports - generates coverage artifacts

Linter alone

  1. $ yarn lint

how did this get made?

This outlines a large portion of basic beginning setup, but is no longer being extended. Subject to deletion.

Create basic app

  1. $ express exp-starter-app and cd into the created directory
  2. $ yarn install
  3. $ git init
  4. $ echo node_modules/ >> .gitignore
  5. $ touch README.md and start taking amazing notes

Remove all code not needed for API

  1. delete public directory
  2. delete views directory
  3. within app.js:
    • remove all lines referencing favicon
    • remove lines referencing views and view engine setup
    • remove line referencing static files, loading public directory
    • change res.render to res.json within the error handler, with the following argument:
      {
        message: err.message,
        error: err,
      }
    • remove unnecessary comments
    • go to an undefined url to see the proper json error
  4. within routes:
    • index: change response to res.send("oh hai");
    • users: change response to res.json({users: []});
    • add res.status(200); above both of the responses in these files
    • visit these routes to ensure all is well
  5. within package.json:
    • remove jade, serve-favicon
    • don't forget to remove trailing commas!
    • delete node_modules and yarn.lock and re-yarn install
  6. click all the things again just to be sure!

Testing is the best-thing

  1. create a test directory
  2. create a test/features directory
  3. $ touch test/features/welcome.test.js and add the following content:
    const expect = require('expect');
    const request = require('supertest');
    
    const app = require('../../app');
    
    describe('Root of API', () => {
      it('welcomes visitors', async () => {
        const res = await request(app)
          .get('/')
          .expect(200);
    
        expect(res.text).toEqual("failing! oh hai");
        expect(res.body).toEqual({});
      });
    });
  4. add a test script to the package.json with the value: "mocha --recursive"
  5. $ yarn add mocha --dev
  6. $ yarn add expect --dev
  7. $ yarn add supertest --dev
  8. once you get a proper fail, update res.text to pass
  9. repeat similarly for users

Connect PostgreSQL (through model test for User... this is a doozy!)

  1. create a new file test/models/user.test.js with the following content:

    const expect = require('expect');
    
    const User = require('../../models/user.js')
    
    describe('User', () => {
      it('can be created', async () => {
        const usersBefore = await User.all();
        expect(usersBefore.length).toBe(0);
    
        await User.create({
          firstName: 'Elowyn',
          lastName: 'Platzer Bartel',
          email: '[email protected]',
          birthYear: 2015,
          student: true,
          password: 'password',
        })
        const usersAfter = await User.all();
        expect(usersAfter.length).toBe(1);
      });
    });
  2. Create the model, and add the following content:

    const query = require('../db/index').query;
    
    module.exports = {
      all: async () => {
        const users = (await query('SELECT * FROM "users"')).rows;
        return users;
      },
    }
  3. Create the db pool file, and add the following content:

    const { Pool } = require('pg');
    
    const config = require('../dbConfig');
    
    const pool = new Pool(config);
    
    module.exports = {
      query: (text, params) => pool.query(text, params)
    };
  4. $ yarn add pg

  5. Create the dbConfig.js file and add the following content:

    const url = require('url');
    
    const params = url.parse(process.env.DATABASE_URL);
    const auth = params.auth ? params.auth.split(':') : []
    
    module.exports = {
      user: auth[0],
      password: auth[1],
      host: params.hostname,
      port: params.port,
      database: params.pathname.split('/')[1],
    };
  6. Create .env.example with the following content, then copy to .env:

    DATABASE_URL=postgres://localhost/exp_starter_app_development
    TEST_DATABASE_URL=postgres://localhost/exp_starter_app_test
    
  7. $ yarn add dotenv --dev

  8. Create test/helpers.js file with the following content:

    require('dotenv').config();
    
    process.env.NODE_ENV = 'test';
    process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  9. $ createdb exp_starter_app_test

  10. $ createdb exp_starter_app_development

  11. Create migration for users table:

    • create files
      • migrations/<year><month><day><hour><minutes><seconds>.do.<description>.sql with the following content:
        CREATE TABLE IF NOT EXISTS "users"(
          "id"                              SERIAL            PRIMARY KEY  NOT NULL,
          "firstName"                       VARCHAR(100)      NOT NULL,
          "lastName"                        VARCHAR(100)      NOT NULL,
          "email"                           VARCHAR(200)      NOT NULL,
          "birthYear"                       INT,
          "student"                         BOOLEAN           NOT NULL DEFAULT FALSE,
          "passwordDigest"                  VARCHAR(100)      NOT NULL,
          "createdAt"                       TIMESTAMP         NOT NULL DEFAULT CURRENT_TIMESTAMP,
          "updatedAt"                       TIMESTAMP         NOT NULL DEFAULT CURRENT_TIMESTAMP
        );
      • migrations/<same timestamp ^^ >.undo.<same description ^^ >.sql with the following content:
        DROP TABLE IF EXISTS "users";
    • add the migration scripts to package.json:
      "db:migrate": "node postgrator.js",
      "db:migrate:test": "NODE_ENV=test node postgrator.js",
    • add the postgrator.js file with the following content:
      if (process.env.NODE_ENV !== 'production') {
        require('dotenv').config();
      }
      if (process.env.NODE_ENV === 'test') {
        process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
      }
      const postgrator = require('postgrator');
      
      postgrator.setConfig({
        migrationDirectory: __dirname + '/migrations',
        driver: 'pg',
        connectionString: process.env.DATABASE_URL,
      });
      
      // migrate to version specified, or supply 'max' to go all the way up
      postgrator.migrate('max', function(err, migrations) {
        if (err) {
          console.log(err);
        } else {
          if (migrations) {
            console.log(
              ['*******************']
                .concat(migrations.map(migration => `checking ${migration.filename}`))
                .join('\n')
            );
          }
        }
        postgrator.endConnection(() => {});
      });
    • $ yarn add postgrator
    • $ yarn db:migrate and $ yarn db:migrate:test
  12. add create property to the user model with the following async function content:

    const saltRounds = 10;
    const salt = bcrypt.genSaltSync(saltRounds);
    const passwordDigest = bcrypt.hashSync(properties.password, salt);
    
    const createdUser = (await query(
      `INSERT INTO "users"(
        "firstName",
        "lastName",
        "email",
        "birthYear",
        "student",
        "passwordDigest"
      ) values ($1, $2, $3, $4, $5, $6) returning *`,
      [
        properties.firstName,
        properties.lastName,
        properties.email,
        properties.birthYear,
        properties.student,
        passwordDigest,
      ]
    )).rows[0];
    return createdUser;
  13. at the top of the user model file, add:

    const bcrypt = require('bcryptjs');
  14. $ yarn add bcryptjs

  15. Run your tests twice or more while passing... Oh no! No database cleanup after test runs!

  • Add to bottom of test helpers:
    const clearDB = require('../lib/clearDB');
    afterEach(clearDB);
  • create lib/clearDB.js file with the following content:
    const query = require('../db/index').query;
    
    module.exports = async () => {
      await query('delete from "users"');
    };

Add signup route

  1. Write the test in features/users.test.js:
    it('can signup and receive a JWT', async () => {
      const res = await request(app)
        .post('/users')
        .send({
          firstName: 'Elowyn',
          lastName: 'Platzer Bartel',
          email: '[email protected]',
          birthYear: 2015,
          student: true,
          password: 'password',
        })
        .expect(200);
    
      expect(res.body.jwt).not.toBe(undefined);
      expect(res.body.user.id).not.toBe(undefined);
      expect(res.body.user.firstName).toEqual('Elowyn');
      expect(res.body.user.lastName).toEqual('Platzer Bartel');
      expect(res.body.user.email).toEqual('[email protected]');
      expect(res.body.user.birthYear).toEqual(2015);
      expect(res.body.user.student).toEqual(true);
    
      expect(res.body.user.passwordDigest).toEqual(undefined);
      expect(res.body.user.createdAt).toEqual(undefined);
      expect(res.body.user.updatedAt).toEqual(undefined);
    });
  2. Add to the users routes: router.post('/', usersController.create); and const usersController = require('../controllers/users') at the top
  3. Create the controllers/user with the following content:
    const jwt = require('jsonwebtoken');
    
    const userSerializer = require('../serializers/user');
    const User = require('../models/user');
    
    module.exports = {
      create: async (req, res, next) => {
        const user = await User.create(req.body);
        const serializedUser = await userSerializer(user);
        const token = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
        res.json({ jwt: token, user: serializedUser });
      }
    }
  4. Add the JWT_SECRET to the .env.example and .env. Value doesn't really matter as long as it's the same to encode and decode the JWTs
  5. $ yarn add jsonwebtoken
  6. You will likely or eventually need to require helpers.js at the top of each test file (above everything except the package dependencies). If all tests are run, you will only need it to be required in a preceding run file, but if you run a single test yarn test test/models/user.test.js you will be missing that requirement.
  7. Add serializers/user.js with the following content:
    module.exports = user => {
      const serialized = {
        id: user.id,
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        birthYear: user.birthYear,
        student: user.student,
      };
      return serialized;
    };
  8. Try curling the signup route (see curl docs)
    • add if (process.env.NODE_ENV !== 'production') { require('dotenv').config() } to the top of the bin/www file and restart the server if needed

Update the users route

We left the users route returning an empty array. Let's update that test and drive the rewrite to make this actually query the database.

  1. Update the feature test for users index:
    it('can be listed, without users and with one added', async () => {
      const resNoUsers = await request(app)
        .get('/users')
        .expect(200);
      expect(resNoUsers.body).toEqual({users: []});
    
      await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      })
    
      const resWithUsers = await request(app)
        .get('/users')
        .expect(200);
    
      expect(resWithUsers.body.users.length).toEqual(1);
      const newUser = resWithUsers.body.users[0]
      expect(resWithUsers.jwt).toBe(undefined);
      expect(newUser.id).not.toBe(undefined);
      expect(newUser.firstName).toEqual('Elowyn');
      expect(newUser.lastName).toEqual('Platzer Bartel');
      expect(newUser.email).toEqual('[email protected]');
      expect(newUser.birthYear).toEqual(2015);
      expect(newUser.student).toEqual(true);
    
      expect(newUser.passwordDigest).toEqual(undefined);
      expect(newUser.createdAt).toEqual(undefined);
      expect(newUser.updatedAt).toEqual(undefined);
    });
  2. Require the User model in the top of the test file
  3. Update the users index route to: router.get('/', usersController.index);
  4. Update the users controller to add the index action like so:
    index: async (req, res, next) => {
      const users = await User.all();
      const serializedUsers = users.map(user => userSerializer(user));
      res.json({ users: serializedUsers });
    },

Users can log in and receive a JWT

  1. Add a features/authentication.test.js with the following content:
    const expect = require('expect');
    const request = require('supertest');
    
    require('../helpers')
    
    const app = require('../../app');
    
    const User = require('../../models/user')
    
    describe('Authentication - ', () => {
      it('users can log in and receive a JWT', async () => {
        const userParams = {
          firstName: 'Elowyn',
          lastName: 'Platzer Bartel',
          email: '[email protected]',
          birthYear: 2015,
          student: true,
          password: 'password',
        };
    
        const user = await User.create(userParams);
        const res = await request(app)
          .post('/login')
          .send({ email: '[email protected]', password: 'password' })
          .expect(200);
        expect(res.body.jwt).not.toBe(undefined);
        expect(res.body.user).toEqual({
          id: user.id,
          firstName: 'Elowyn',
          lastName: 'Platzer Bartel',
          email: '[email protected]',
          birthYear: 2015,
          student: true,
        });
        expect(res.body.user.passwordDigest).toEqual(undefined);
        expect(res.body.user.createdAt).toEqual(undefined);
        expect(res.body.user.updatedAt).toEqual(undefined);
      });
    });
  2. Add the login route to app.js, and the login route file with the following content:
    const express = require('express');
    const router = express.Router();
    
    const loginController = require('../controllers/login')
    
    router.post('/', loginController.create);
    
    module.exports = router;
  3. Add the login controller with the following content:
    const User = require('../models/user');
    
    exports.create = async (req, res, next) => {
      res.json(await User.authenticate(req.body));
    };
  4. Add the authenticate method to the User model:
    authenticate: async credentials => {
      const user = (await query('SELECT * FROM "users" WHERE "email" = ($1)', [
        credentials.email,
      ])).rows[0];
    
      const valid = user
        ? await bcrypt.compare(credentials.password, user.passwordDigest)
        : false;
      if (valid) {
        const serializedUser = await userSerializer(user);
        const token = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
        return { jwt: token, user: serializedUser };
      } else {
        return { errors: ['Email or Password is incorrect'] };
      }
    },

Require Authentication for the User index

We don't want to allow just anybody to get a list of users. Let's lock this route down.

  1. Update the user index feature test:
    it('can be listed for a logged in user', async () => {
      const user = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
      serializedUser = await userSerializer(user);
      token = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
    
      const resNotLoggedIn = await request(app)
      .get('/users')
      .expect(404);
    
      const resLoggedIn = await request(app)
        .get('/users')
        .set('jwt', token)
        .expect(200);
    
      expect(resLoggedIn.body.users.length).toEqual(1);
      const newUser = resLoggedIn.body.users[0]
      expect(resLoggedIn.jwt).toBe(undefined);
      expect(newUser.id).not.toBe(undefined);
      expect(newUser.firstName).toEqual('Elowyn');
      expect(newUser.lastName).toEqual('Platzer Bartel');
      expect(newUser.email).toEqual('[email protected]');
      expect(newUser.birthYear).toEqual(2015);
      expect(newUser.student).toEqual(true);
      expect(newUser.passwordDigest).toEqual(undefined);
      expect(newUser.createdAt).toEqual(undefined);
      expect(newUser.updatedAt).toEqual(undefined);
    });
  2. reorder user routes and add the verifyLoggedInUser middleware, required at the top from a lib/verifyLoggedInUser.js:
    router.post('/', usersController.create);
    
    router.use(verifyLoggedInUser);
    
    router.get('/', usersController.index);
  3. Add the verifyLoggedInUser file with the following content:
    const jwt = require('jsonwebtoken');
    
    const currentUser = require('./currentUser');
    
    module.exports = (req, res, next) => {
      const token = req.headers.jwt;
      if (!currentUser(token)) {
        const err = new Error('Not Found');
        err.status = 404;
        next(err);
      }
      next();
    };
  4. Add the currentUser file with the following content:
    const jwt = require('jsonwebtoken');
    
    module.exports = token => {
      try {
        return jwt.verify(token, process.env.JWT_SECRET).user;
      } catch (err) {
        return undefined
      }
    };

Uniformity refactor

  • Refactored vars into consts (and lets where necessary)
  • Refactored module.exports to exports.method

Adding prettier

  1. $ yarn add prettier --dev
  2. add script to package.json: "prettier": "prettier --single-quote --trailing-comma=es5 --list-different --write es5 './**/*.js'",
  3. run prettier and approve diffs if you like them!

User can be found by a property

  1. Add a model test:
    it('can be found by property', async () => {
      const user = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
    
      const foundUser = await User.findBy({ email: '[email protected]' });
      expect(foundUser.firstName).toEqual('Elowyn');
      expect(foundUser.lastName).toEqual('Platzer Bartel');
      expect(foundUser.email).toEqual('[email protected]');
      expect(foundUser.birthYear).toEqual(2015);
      expect(foundUser.student).toEqual(true);
    });
  2. Add the method to the User model:
    exports.findBy = async property => {
      const key = Object.keys(property)[0];
      let findByQuery;
      switch (key) {
        case 'firstName':
          findByQuery = 'SELECT * FROM "users" WHERE "firstName" = $1 LIMIT 1';
          break;
        case 'lastName':
          findByQuery = 'SELECT * FROM "users" WHERE "lastName" = $1 LIMIT 1';
          break;
        case 'email':
          findByQuery = 'SELECT * FROM "users" WHERE "email" = $1 LIMIT 1';
          break;
        case 'birthYear':
          findByQuery = 'SELECT * FROM "users" WHERE "birthYear" = $1 LIMIT 1';
          break;
        case 'student':
          findByQuery = 'SELECT * FROM "users" WHERE "student" = $1 LIMIT 1';
          break;
      };
    
      const value = property[key];
      const user = (await query(findByQuery, [value])).rows[0];
      return user;
    };

User must have a unique email

  1. Add a model test:

    it('must have unique email', async () => {
      await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
      const duplicateUser = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
    
      expect(duplicateUser).toEqual(['Email already taken'])
      const users = await User.all();
      expect(users.length).toBe(1);
    });
  2. Add the validation to the User model. Note, for extensibility of validations, add more if statements. Validations will get pushed to the errors array and finally be returned:

    const errors = [];
    if (await this.findBy({email: properties.email})) {
      const error = 'Email already taken'
      errors.push(error);
    };
    if (errors.length > 0) { return errors };

Test the current user lib function independently

  1. Add the happy path test, "break it" to see a good red, then allow it to pass.
    const expect = require('expect');
    const jwt = require('jsonwebtoken');
    
    require('../helpers/testSetup');
    
    const currentUser = require('../../lib/currentUser');
    const User = require('../../models/user');
    const userSerializer = require('../../serializers/user');
    
    describe('currentUser', () => {
      it('returns a User when passed a valid token', async () => {
        const createdUser = await User.create({
          firstName: 'Elowyn',
          lastName: 'Platzer Bartel',
          email: '[email protected]',
          birthYear: 2015,
          student: true,
          password: 'password',
        });
        const serializedUser = userSerializer(createdUser);
        const validToken = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
    
        const user = currentUser(validToken);
        expect(user).toEqual(serializedUser); //break it here for example with `.toEqual(createdUser)`
      });
    });
  2. Add the sad path test...
    it('returns undefined when passed an invalid token', async () => {
      const invalidToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
    
      const user = currentUser(invalidToken);
      expect(user).toEqual(undefined);
    });
  3. Add tests for user login sad path (auth feature test):
    it('users cannot login without valid credentials', async () => {
      const userParams = {
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      };
    
      const user = await User.create(userParams);
      const wrongPasswordRes = await request(app)
        .post('/login')
        .send({ email: '[email protected]', password: 'wrong password' })
        .expect(200);
      expect(wrongPasswordRes.body.jwt).toBe(undefined);
      expect(wrongPasswordRes.body.user).toEqual(undefined);
      expect(wrongPasswordRes.body.error).toEqual(['Email or Password is incorrect']);
    
      const noUserRes = await request(app)
        .post('/login')
        .send({ email: '[email protected]', password: 'password' })
        .expect(200);
      expect(noUserRes.body.jwt).toBe(undefined);
      expect(noUserRes.body.user).toEqual(undefined);
      expect(noUserRes.body.errors).toEqual(['Email or Password is incorrect']);
    });

User find

  1. Add the model test:
    it('can be found by id', async () => {
      const user = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
    
      const foundUser = await User.find(user.id);
      expect(foundUser.firstName).toEqual('Elowyn');
      expect(foundUser.lastName).toEqual('Platzer Bartel');
      expect(foundUser.email).toEqual('[email protected]');
      expect(foundUser.birthYear).toEqual(2015);
      expect(foundUser.student).toEqual(true);
    });

User show for a logged in user

  1. Add the feature test:

    it('can be shown for a logged in user only', async () => {
      const user = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
      serializedUser = await userSerializer(user);
      token = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
    
      const resNotLoggedIn = await request(app)
        .get(`/users/${user.id}`)
        .expect(404);
    
      const resLoggedIn = await request(app)
        .get(`/users/${user.id}`)
        .set('jwt', token)
        .expect(200);
    
      const showUser = resLoggedIn.body.user;
      expect(resLoggedIn.jwt).toBe(undefined);
      expect(showUser.id).not.toBe(undefined);
      expect(showUser.firstName).toEqual('Elowyn');
      expect(showUser.lastName).toEqual('Platzer Bartel');
      expect(showUser.email).toEqual('[email protected]');
      expect(showUser.birthYear).toEqual(2015);
      expect(showUser.student).toEqual(true);
    
      expect(showUser.passwordDigest).toEqual(undefined);
      expect(showUser.createdAt).toEqual(undefined);
      expect(showUser.updatedAt).toEqual(undefined);
    });
  2. Add the route and controller action:

    router.get('/:id', usersController.show);
    exports.show = async (req, res, next) => {
      const user = await User.find(req.params.id);
      const serializedUser = await userSerializer(user);
      res.json({ user: serializedUser });
    }

Update show for "no user found with that id"

  1. Update the test description to "can be shown with a valid user id for a logged in user only" and add the following request between the two requests of the last test:
    const resLoggedInWrongId = await request(app)
      .get(`/users/${user.id+10}`)
      .set('jwt', token)
      .expect(404);
  2. Update users controller show action to:
    exports.show = async (req, res, next) => {
      try {
        const user = await User.find(req.params.id);
        const serializedUser = await userSerializer(user);
        res.json({ user: serializedUser });
      } catch (e) {
        const err = new Error('Not Found');
        err.status = 404;
        next(err);
      }
    }

Feature test for User tries to login with duplicate email

  1. Add to end of create user feature test:
    const duplicateEmailRes = await request(app)
      .post('/users')
      .send({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      })
      .expect(200);
    
      expect(duplicateEmailRes.body.jwt).toBe(undefined);
      expect(duplicateEmailRes.body.user.id).toBe(undefined);
      expect(duplicateEmailRes.body.user.errors).toEqual(['Email already taken']);
  2. Update the create user action in the controller:
    const user = await User.create(req.body);
    if (user.errors) {
      res.json({ user: user });
    } else {
      const serializedUser = await userSerializer(user);
      const token = jwt.sign({ user: serializedUser }, process.env.JWT_SECRET);
      res.json({ jwt: token, user: serializedUser });
    }

Model - User can be updated

  1. model test:
    it('can be updated', async () => {
      const originalUser = await User.create({
        firstName: 'Elowyn',
        lastName: 'Platzer Bartel',
        email: '[email protected]',
        birthYear: 2015,
        student: true,
        password: 'password',
      });
      const updatedUser = await User.update({
        id: originalUser.id,
        firstName: 'Freyja',
        lastName: 'Puppy',
        email: '[email protected]',
        birthYear: 2016,
        student: false,
        password: 'puppy password',
      })
    
      expect(updatedUser.firstName).toBe('Freyja');
      expect(updatedUser.lastName).toBe('Puppy');
      expect(updatedUser.email).toBe('[email protected]');
      expect(updatedUser.birthYear).toBe(2016);
      expect(updatedUser.student).toBe(false);
      expect(updatedUser.passwordDigest).not.toBe(originalUser.passwordDigest);
    });
  2. model method:
    exports.update = async properties => {
      const saltRounds = 10;
      const salt = bcrypt.genSaltSync(saltRounds);
      const passwordDigest = bcrypt.hashSync(properties.password, salt);
    
      const updatedUser = (await query(`UPDATE "users" SET
        "firstName"=($1),
        "lastName"=($2),
        "email"=($3),
        "birthYear"=($4),
        "student"=($5),
        "passwordDigest"=($6) WHERE id=($7) RETURNING *`, [
        properties.firstName,
        properties.lastName,
        properties.email,
        properties.birthYear,
        properties.student,
        passwordDigest,
        properties.id,
      ])).rows[0];
    
      return updatedUser;
    }

And so on...

exp-starter-api's People

Contributors

07nguyenpaul avatar cognizantmickey avatar craftninja avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.