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

Eric Whitcomb #481

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0a8301d
Forked, cloned, and set up Trello board
ericwhitcomb Feb 12, 2019
bbd3d3d
Created node project
ericwhitcomb Feb 13, 2019
cf282f2
Added dependencies
ericwhitcomb Feb 13, 2019
47d72a9
Added scripts to package.json
ericwhitcomb Feb 13, 2019
57d3439
Server is running and tests run successfully
ericwhitcomb Feb 13, 2019
6b4c5e1
Set up knex and migrations
ericwhitcomb Feb 13, 2019
31a3226
Created unit tests for note model
ericwhitcomb Feb 14, 2019
45256ea
Updated unit tests for note model
ericwhitcomb Feb 14, 2019
d0c3364
Formatted note model unit test document
ericwhitcomb Feb 14, 2019
4b4969f
Continuing work on unit tests for model
ericwhitcomb Feb 14, 2019
530bda4
Insert, get all, and get by id in note model passing all unit tests
ericwhitcomb Feb 14, 2019
338e34c
note model update and remove unit tests passing
ericwhitcomb Feb 15, 2019
2fcc016
Adding unit tests for endpoints
ericwhitcomb Feb 15, 2019
931649a
Straightening up posr endpoint for notes
ericwhitcomb Feb 15, 2019
680b54a
Post and get request unit tests done
ericwhitcomb Feb 16, 2019
0c4db34
Put and delete request unit tests done
ericwhitcomb Feb 16, 2019
e925484
Get and post tests passing for note api
ericwhitcomb Feb 16, 2019
a3e7ca0
Put and delete tests passing for note api
ericwhitcomb Feb 16, 2019
dd56ca6
Updated readme
ericwhitcomb Feb 16, 2019
5febded
Added cors
ericwhitcomb Feb 16, 2019
a61812e
Updated readme
ericwhitcomb Feb 17, 2019
5904eef
Updated port to include env variable
ericwhitcomb Feb 18, 2019
e35ad70
Updated app to show port in web browser
ericwhitcomb Feb 18, 2019
e587316
Set up postgres for production environment
ericwhitcomb Feb 19, 2019
0a40529
Updated insert in noteModel to work with postgres
ericwhitcomb Feb 19, 2019
115ae3f
Adding passportjs with github strategy
ericwhitcomb Feb 22, 2019
2b7af4c
Added env file
ericwhitcomb Feb 22, 2019
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules
.DS_Store
.vscode
.env
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,31 @@ You are required to showcase progress with at least 4 commits a day. This will l

## Trello Set Up

- Use your existing Trello account from the Front End Project, or create a new one.
- Create a new board called "Lambda Notes(Backend) - {Your Name}".
- Create lists titled `Backlog`,`To do`, `Blocked`, `In Progress`, and `Done`.
- Fill in the `To do` list with the MVP features listed below.
- Fill in the `backlog` list with all the extra features listed below.
- Share your board with the project manager that has been assigned to you. If you have not been assigned yet, reach out to your Section Lead for guidance.
- Add your Trello URL to your project's README.md file. Commit the change, push it to your repository & submit a pull request.
- [x] Use your existing Trello account from the Front End Project, or create a new one.
- [x] Create a new board called "Lambda Notes(Backend) - {Your Name}".
- [x] Create lists titled `Backlog`,`To do`, `Blocked`, `In Progress`, and `Done`.
- [x] Fill in the `To do` list with the MVP features listed below.
- [x] Fill in the `backlog` list with all the extra features listed below.
- [x] Share your board with the project manager that has been assigned to you. If you have not been assigned yet, reach out to your Section Lead for guidance.
- [x] Add your Trello URL to your project's README.md file. Commit the change, push it to your repository & submit a pull request.

### My Trello Board

https://trello.com/b/n9dTfZDD/lambda-notes-backend-eric-whitcomb

## Backend MVP Features:

We recommend that you finish all the MVP features before trying to deploy.

- Add data persistenc using a Relational Database. We suggest you start with `SQLite3`.
- Create a Web API for the React application you built in the front-end project week.
- Build endpoints for each of the following features:
- Display a list of notes.
- Create a note with a _title_ and _content_.
- View an existing note.
- Edit an existing note.
- Delete an existing note.
- Modify your front-end so that it uses your newly created Web API.
- [x] Add data persistence using a Relational Database. We suggest you start with `SQLite3`.
- [x] Create a Web API for the React application you built in the front-end project week.
- [x] Build endpoints for each of the following features:
- [x] Display a list of notes.
- [x] Create a note with a _title_ and _content_.
- [x] View an existing note.
- [x] Edit an existing note.
- [x] Delete an existing note.
- [x] Modify your front-end so that it uses your newly created Web API.

Upon your first commit, please submit a Pull Request and add _both_ the **Trello Set Up** and **Backend MVP Features** Task lists to your first Pull Request comment.

Expand Down
47 changes: 47 additions & 0 deletions api/noteRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const express = require('express');
const noteModel = require('../data/models/noteModel');
const router = express.Router();

router.post('/', async (req, res) => {
const note = req.body;
if (note.title && note.content) {
res.status(201).json(await noteModel.insert(note));
} else {
res.status(400).json({ error: 'Note must contain title and content' });
}
});

router.get('/', async (req, res) => {
const note = await noteModel.get()
res.status(200).json(note);
});

router.get('/:id', async (req, res) => {
try {
const note = await noteModel.get(req.params.id);
res.status(200).json(note);
} catch (e) {
res.status(404).json({error: "Invalid id"});
}
});

router.put('/:id', async (req, res) => {
try {
const note = await noteModel.update(req.params.id, req.body);
res.status(200).json(note);
} catch (e) {
res.status(404).json({error: "Invalid id"});
}

});

router.delete('/:id', async (req, res) => {
try {
const count = await noteModel.remove(req.params.id);
res.status(200).json({ count });
} catch (e) {
res.status(404).json({error: "Invalid id"});
}
});

module.exports = router;
10 changes: 10 additions & 0 deletions api/rootRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();

router.use('/notes', require('./noteRouter'));

router.get('/', (req, res) => {
res.json({api: "active"});
});

module.exports = router;
15 changes: 15 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const express = require('express');
var cors = require('cors');
const app = express();

app.use(express.json());
app.use(cors());
app.use('/api', require('./api/rootRouter'));

const PORT = process.env.PORT || '3300';

app.get('/', (req, res) => {
res.status(200).send(`API active on port: ${PORT}`);
});

module.exports = app;
6 changes: 6 additions & 0 deletions data/dbConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const knex = require('knex');
const config = require('../knexfile.js');

const dbEnv = process.env.DB_ENV || 'development';

module.exports = knex(config[dbEnv]);
Binary file added data/dev.sqlite3
Binary file not shown.
11 changes: 11 additions & 0 deletions data/migrations/20190212222212_notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports.up = function(knex, Promise) {
return knex.schema.createTable('notes', (notes) => {
notes.increments();
notes.string('title').notNullable();
notes.text('content').notNullable();
});
};

exports.down = function(knex, Promise) {
return knex.schema.dropTableIfExists('notes');
};
138 changes: 138 additions & 0 deletions data/models/noteModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const db = require('../dbConfig.js');

const get = async (id) => {
if (id) {
const note = await db('notes').where('id', id);

Choose a reason for hiding this comment

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

I'm pretty sure you can use .first()? instead of having that if statement

Suggested change
const note = await db('notes').where('id', id);
const note = await db('notes').where('id', id).first();

if (note.length) {
return note[0];
} else {
const e = new Error("id does not exist");

Choose a reason for hiding this comment

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

Might be easiest just to do this all on one line

Suggested change
const e = new Error("id does not exist");
throw new Error("id does not exist");

Choose a reason for hiding this comment

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

Then you won't need the next two lines

e.name = "InvalidID";
throw e;
}
}

const notes = await db('notes');
return notes;
};

const insert = async (note) => {

// check for missing note object
if (typeof note === 'undefined') {

Choose a reason for hiding this comment

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

I don't think you need this check 🤔 is there ever a case where this would happen?

const e = new Error("note object");
e.name = "MissingParam";
throw e;
}

// check if note is object
if (typeof note !== 'object') {

Choose a reason for hiding this comment

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

Same for this. Would this ever not be an object?

const e = new TypeError("note is not an object");
throw e;
}

// check for missing title key
if (!note.hasOwnProperty('title')) {
Copy link

@KingAtoki KingAtoki Feb 22, 2019

Choose a reason for hiding this comment

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

Since note isn't inherited, you are fine with just checking !note.title

Suggested change
if (!note.hasOwnProperty('title')) {
if (!note.title) {

Side Note: Now if it something like

const originalNote = { title: 'Here is a title', content: 'Some content' }

const note = originalNote;

console.log(note.hasOwnProperty('title')) // false because it is inherited

note.tag = 'here is a tag'

console.log(note.hasOwnProperty('tag)) // true

const e = new Error("title");
e.name = "MissingKey";
throw e;
}

// check for missing content key
if (!note.hasOwnProperty('content')) {
Copy link

@KingAtoki KingAtoki Feb 22, 2019

Choose a reason for hiding this comment

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

Suggested change
if (!note.hasOwnProperty('content')) {
if (!note.content) {

const e = new Error("content");
e.name = "MissingKey";
throw e;
}

// check for title not string
if (typeof note.title !== 'string') {

Choose a reason for hiding this comment

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

I think this will always be a string. Even if the user types in a number like 1 it will still be a string unless you change it to a number lol

const e = new TypeError("'title' value must be string");
throw e;
}

// check for content not string
if (typeof note.content !== 'string') {

Choose a reason for hiding this comment

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

Same thing here

const e = new TypeError("'content' value must be string");
throw e;
}

const ids = await db('notes').insert(note, 'id');
const n = await get(ids[0]);
return n;
};

const update = async (id, note) => {

Choose a reason for hiding this comment

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

Same thing as above. Double check to make sure you aren't making unnecessary checks


// check for missing id
if (typeof id === 'undefined') {
const e = new Error("note id");
e.name = "MissingParam";
throw e;
}

// check for missing note object
if (typeof note === 'undefined') {
const e = new Error("note object");
e.name = "MissingParam";
throw e;
}

// check if note is object
if (typeof note !== 'object') {
const e = new TypeError("note is not an object");
throw e;
}

// check for empty note object
if (!note.hasOwnProperty('title') && !note.hasOwnProperty('content')) {
const e = new Error("note object missing 'title' and 'content'");
e.name = "EmptyObject";
throw e;
}

// check for title not string
if (note.hasOwnProperty('title') && typeof note.title !== 'string') {
const e = new TypeError("'title' value must be string");
throw e;
}

// check for content not string
if (note.hasOwnProperty('content') && typeof note.content !== 'string') {
const e = new TypeError("'content' value must be string");
throw e;
}

const count = await db('notes').where('id', id).update(note);
Copy link

@KingAtoki KingAtoki Feb 22, 2019

Choose a reason for hiding this comment

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

This is a good time to have a `try/catch block to organize your code

try {
  await db('notes').where('id', id).update(note); // <-- if this fails it will automatically go to the catch block
  const n = await get(id);
  return n;
} catch (e) {
  throw new Error("id does not exist");
}

if (count) {
const n = await get(id);
return n;
} else {
const e = new Error("id does not exist");
e.name = "InvalidID";
throw e;
}
};

const remove = async (id) => {

// check for missing id
if (typeof id === 'undefined') {
const e = new Error("note id");
e.name = "MissingParam";
throw e;
}

const count = await db('notes').where('id', id).del();
if (count) {
return count;
} else {
const e = new Error("id does not exist");
e.name = "InvalidID";
throw e;
}
};

module.exports = {
insert, get, update, remove
}
Binary file added data/test.sqlite3
Binary file not shown.
57 changes: 57 additions & 0 deletions knexfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Update with your config settings.

const localPgConnection = {
host: 'localhost',
database: 'prod',
user: 'notes',
password: 'blah1234',
}

const dbConnection = process.env.DATABASE_URL || localPgConnection;

module.exports = {

development: {
client: 'sqlite3',
connection: {
filename: './data/dev.sqlite3'
},
useNullAsDefault: true,
migrations: {
directory: './data/migrations',
},
seeds: {
directory: './data/seeds',
},
},

production: {
client: 'pg',
connection: dbConnection,
pool: {
min: 2,
max: 10
},
migrations: {
directory: './data/migrations',
},
seeds: {
directory: './data/seeds',
},
},

testing: {
client: 'sqlite3',
connection: {
filename: './data/test.sqlite3'
},
useNullAsDefault: true,
migrations: {
directory: './data/migrations',
},
seeds: {
directory: './data/seeds',
},
},

};
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "back-end-project-week",
"version": "1.0.0",
"main": "server.js",
"repository": "https://github.com/ericwhitcomb/back-end-project-week.git",
"author": "Eric Whitcomb <[email protected]>",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^6.2.0",
"express": "^4.16.4",
"knex": "^0.16.3",
"passport": "^0.4.0",
"passport-github2": "^0.1.11",
"pg": "^7.8.0",
"sqlite3": "^4.0.6"
},
"devDependencies": {
"cross-env": "^5.2.0",
"jest": "^24.1.0",
"nodemon": "^1.18.10",
"supertest": "^3.4.2"
},
"scripts": {
"server": "nodemon server.js",
"test": "cross-env DB_ENV=testing jest --watch --verbose"
},
"jest": {
"testEnvironment": "node"
}
}
9 changes: 9 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require('dotenv').config();

const app = require('./app');

const PORT = process.env.PORT || '3300';

app.listen(PORT, () => {
console.log(`\nListening on port ${PORT}\n`);
});
Loading