Skip to content

Commit ffe7948

Browse files
committed
✨ init
0 parents  commit ffe7948

Some content is hidden

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

47 files changed

+4664
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
.DS_Store
3+
*.log
4+
*.error
5+
dist
6+
.webpack

README.md

Whitespace-only changes.

package.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "api-node",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "webpack --colors --progress",
8+
"test:unit": "NODE_ENV=testing rm -rf ./tmp/mocha-webpack && mocha-webpack --webpack-config webpack.testing.js \"src/**/*.spec.js\" --timeout 10000 --require source-map-support/register",
9+
"test": "npm run test:unit"
10+
},
11+
"dependencies": {
12+
"apollo-server-express": "^1.1.6",
13+
"bcrypt": "^1.0.3",
14+
"express": "^4.16.1",
15+
"express-jwt": "^5.3.0",
16+
"graphql-tools": "^2.4.0",
17+
"jsonwebtoken": "^8.1.0",
18+
"lodash.merge": "^4.6.0",
19+
"mongoose": "^4.12.1"
20+
},
21+
"devDependencies": {
22+
"babel-core": "^6.26.0",
23+
"babel-loader": "^7.1.2",
24+
"babel-plugin-transform-regenerator": "^6.26.0",
25+
"babel-plugin-transform-runtime": "^6.23.0",
26+
"babel-preset-env": "^1.6.0",
27+
"babel-preset-stage-0": "^6.24.1",
28+
"chai": "^4.1.2",
29+
"chai-http": "^3.0.0",
30+
"graphql-tag": "^2.4.2",
31+
"mocha": "^4.0.1",
32+
"mocha-webpack": "next",
33+
"raw-loader": "^0.5.1",
34+
"source-map-support": "^0.5.0",
35+
"start-server-webpack-plugin": "^2.2.0",
36+
"webpack": "^3.6.0",
37+
"webpack-node-externals": "^1.6.0"
38+
}
39+
}

src/api/graphQLRouter.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { makeExecutableSchema } from 'graphql-tools'
2+
import { userType, userResolvers } from './resources/user'
3+
import merge from 'lodash.merge'
4+
import { graphqlExpress } from 'apollo-server-express'
5+
6+
const baseSchema = `
7+
schema {
8+
query: Query
9+
mutation: Mutation
10+
}
11+
`
12+
13+
const schema = makeExecutableSchema({
14+
typeDefs: [
15+
baseSchema,
16+
userType
17+
],
18+
resolvers: merge(
19+
{},
20+
userResolvers
21+
)
22+
})
23+
24+
25+
export const graphQLRouter = graphqlExpress((req) => ({
26+
schema,
27+
context: {
28+
req,
29+
user: req.user
30+
}
31+
}))

src/api/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { restRouter } from './restRouter'
2+
export { graphQLRouter } from './graphQLRouter'

src/api/modules/auth.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { User } from '../resources/user/user.model'
2+
import jwt from 'jsonwebtoken'
3+
import config from '../../config'
4+
import expressJwt from 'express-jwt'
5+
6+
const checkToken = expressJwt({ secret: config.secrets.JWT_SECRET })
7+
8+
export const signin = (req, res, next) => {
9+
// req.user will be there from the middleware
10+
// verify user. Then we can just create a token
11+
// and send it back for the client to consume
12+
const token = signToken(req.user.id)
13+
res.json({token: token})
14+
}
15+
16+
export const decodeToken = () => (req, res, next) => {
17+
if (config.disableAuth) {
18+
return next()
19+
}
20+
// make it optional to place token on query string
21+
// if it is, place it on the headers where it should be
22+
// so checkToken can see it. See follow the 'Bearer 034930493' format
23+
// so checkToken can see it and decode it
24+
if (req.query && req.query.hasOwnProperty('access_token')) {
25+
req.headers.authorization = 'Bearer ' + req.query.access_token
26+
}
27+
28+
// this will call next if token is valid
29+
// and send error if its not. It will attached
30+
// the decoded token to req.user
31+
checkToken(req, res, next)
32+
}
33+
34+
export const getFreshUser = () => (req, res, next) => {
35+
return User.findById(req.user.id)
36+
.then(function(user) {
37+
if (!user) {
38+
// if no user is found it was not
39+
// it was a valid JWT but didn't decode
40+
// to a real user in our DB. Either the user was deleted
41+
// since the client got the JWT, or
42+
// it was a JWT from some other source
43+
res.status(401).send('Unauthorized')
44+
} else {
45+
// update req.user with fresh user from
46+
// stale token data
47+
req.user = user
48+
next()
49+
}
50+
})
51+
.catch(error => next(error))
52+
}
53+
54+
export const verifyUser = () => (req, res, next) => {
55+
const username = req.body.username
56+
const password = req.body.password
57+
58+
// if no username or password then send
59+
if (!username || !password) {
60+
res.status(400).send('You need a username and password')
61+
return
62+
}
63+
64+
// look user up in the DB so we can check
65+
// if the passwords match for the username
66+
User.findOne({username: username})
67+
.then(function(user) {
68+
if (!user) {
69+
res.status(401).send('No user with the given username')
70+
} else {
71+
// checking the passowords here
72+
if (!user.authenticate(password)) {
73+
res.status(401).send('Wrong password')
74+
} else {
75+
// if everything is good,
76+
// then attach to req.user
77+
// and call next so the controller
78+
// can sign a token from the req.user._id
79+
req.user = user;
80+
next()
81+
}
82+
}
83+
})
84+
.catch(error => next(err))
85+
}
86+
87+
export const signToken = (id) => jwt.sign(
88+
{id},
89+
config.secrets.JWT_SECRET,
90+
{expiresIn: config.expireTime}
91+
)
92+
93+
export const protect = [decodeToken(), getFreshUser()]

src/api/modules/errorHandler.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const apiErrorHandler = (error, req, res, next) => {
2+
console.error(error.stack)
3+
res.status(500).send(error.message || error.toString())
4+
}

src/api/modules/query.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import merge from 'lodash.merge'
2+
3+
export const controllers = {
4+
createOne(model, body) {
5+
return model.create(body)
6+
},
7+
8+
updateOne(docToUpdate, update) {
9+
merge(docToUpdate, update)
10+
return docToUpdate.save()
11+
},
12+
13+
deleteOne(docToDelete) {
14+
return docToDelete.remove()
15+
},
16+
17+
getOne(docToGet) {
18+
return Promise.resolve(docToGet)
19+
},
20+
21+
getAll(model) {
22+
return model.find({})
23+
},
24+
25+
findByParam(model, id) {
26+
return model.findById(id)
27+
}
28+
}
29+
30+
export const createOne = (model) => (req, res, next) => {
31+
return controllers.createOne(model, req.body)
32+
.then(doc => res.status(201).json(doc))
33+
.catch(error => next(error))
34+
}
35+
36+
export const updateOne = (model) => async (req, res, next) => {
37+
const docToUpdate = req.docFromId
38+
const update = req.body
39+
40+
return controllers.updateOne(docToUpdate, update)
41+
.then(doc => res.status(201).json(doc))
42+
.catch(error => next(error))
43+
}
44+
45+
export const deleteOne = (model) => (req, res, next) => {
46+
return controllers.deleteOne(req.docFromId)
47+
.then(doc => res.status(201).json(doc))
48+
.catch(error => next(error))
49+
}
50+
51+
export const getOne = (model) => (req, res, next) => {
52+
return controllers.getOne(req.docToUpdate)
53+
.then(doc => res.status(200).json(doc))
54+
.catch(error => next(error))
55+
}
56+
57+
export const getAll = (model) => (req, res, next) => {
58+
return controllers.getAll(model)
59+
.then(docs => res.json(docs))
60+
.catch(error => next(error))
61+
}
62+
63+
export const findByParam = (model) => (req, res, next, id) => {
64+
return controllers.findByParam(model, id)
65+
.then(doc => {
66+
if (!doc) {
67+
next(new Error('Not Found Error'))
68+
} else {
69+
req.docFromId
70+
next()
71+
}
72+
})
73+
.catch(error => {
74+
next(error)
75+
})
76+
}
77+
78+
79+
export const generateControllers = (model, overrides = {}) => {
80+
const defaults = {
81+
findByParam: findByParam(model),
82+
getAll: getAll(model),
83+
getOne: getOne(model),
84+
deleteOne: deleteOne(model),
85+
updateOne: updateOne(model),
86+
createOne: createOne(model)
87+
}
88+
89+
return {...defaults, ...overrides}
90+
}

src/api/modules/query.spec.js

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { expect } from 'chai'
2+
import { dropDb } from '~/testhelpers'
3+
import { controllers } from './query'
4+
import { User } from '../resources/user/user.model'
5+
6+
describe('Modules', () => {
7+
beforeEach(async () => {
8+
await dropDb()
9+
})
10+
11+
afterEach(async () => {
12+
await dropDb()
13+
})
14+
15+
describe('query', () => {
16+
describe('createOne', () => {
17+
it('should create a document', async () => {
18+
const result = await controllers.createOne(User, {
19+
username: 'student12',
20+
passwordHash: '1234abcd'
21+
})
22+
expect(result).to.be.ok
23+
expect(result.id).to.be.ok
24+
expect(result.username).to.equal('student12')
25+
})
26+
})
27+
describe('updateOne', () => {
28+
it('should update a document', async () => {
29+
const user = await controllers.createOne(User, {
30+
username: 'studentx',
31+
passwordHash: '1234sdkfj'
32+
})
33+
34+
const newUsername = 'newStudentZ'
35+
const updatedUser = await controllers.updateOne(user, {username: newUsername})
36+
37+
expect(updatedUser.username).to.equal(newUsername)
38+
expect(updatedUser.id).to.equal(user.id)
39+
})
40+
})
41+
42+
describe('deleteOne', () => {
43+
it('should delete a document', async () => {
44+
const user = await controllers.createOne(User, {
45+
username: 'studentx',
46+
passwordHash: '1234sdkfj'
47+
})
48+
49+
const deletedUser = await controllers.deleteOne(user)
50+
51+
expect(deletedUser.id).to.equal(user.id)
52+
expect(await User.findById(user.id)).to.equal(null)
53+
})
54+
})
55+
56+
describe('getOne', () => {
57+
it('should get one document', async () => {
58+
const user = await controllers.createOne(User, {
59+
username: 'studentx',
60+
passwordHash: '1234sdkfj'
61+
})
62+
63+
const foundUser = await controllers.getOne(user)
64+
65+
expect(foundUser).to.equal(user)
66+
})
67+
})
68+
69+
describe('getAll', () => {
70+
it('should get all documnets', async () => {
71+
const usernames = ['student1', 'student2']
72+
73+
const users = await Promise.all(usernames.map(async username => {
74+
const user = await controllers.createOne(User, {username, passwordHash: '1234qwer'})
75+
return user.toJSON()
76+
}))
77+
78+
const allUsers = (await controllers.getAll(User))
79+
.map(user => user.toJSON())
80+
81+
expect(allUsers).to.have.length(users.length)
82+
})
83+
})
84+
85+
describe('findByParam', () => {
86+
it('should find model my id', async () => {
87+
const user = (await controllers.createOne(User, {
88+
username: 'student1',
89+
passwordHash: '1234qwe'
90+
}))
91+
.toJSON()
92+
93+
const foundUser = (await controllers.findByParam(User, user._id))
94+
.toJSON()
95+
96+
expect(foundUser).to.deep.eql(user)
97+
})
98+
})
99+
})
100+
})

src/api/resources/playlist/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './playlist.restRouter'
2+
export * from './playlist.graphQLRouter'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { generateControllers } from '../../modules/query'
2+
import { Playlist } from './playlist.model'
3+
4+
export default generateControllers(Playlist)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * as playlistType from './playlist.graphql'
2+
export { playlistResolvers } from './playlist.resolvers'

0 commit comments

Comments
 (0)