Created
April 11, 2021 14:21
-
-
Save lorenzofox3/ca79210d8cf49d44473e89db4616f4e4 to your computer and use it in GitHub Desktop.
separation-of-concern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {createService} from './movies-service.js'; | |
export default async (instance) => { | |
const {Movie} = instance; // mongoose model injected | |
instance.register(async (instance) => { | |
// we overwrite for the scope by our service instead | |
instance.decorate('Movie', createService({model: Movie})); | |
instance.register(routesPlugin); | |
}); | |
} | |
export const routesPlugin = async (instance) => { | |
const {Movie} = instance; // this one will then be the service instead | |
instance.route({ | |
method: 'GET', | |
url: '/:movieSlug', | |
async handler(req, res) { | |
const {params} = req; | |
const movie = await Movie.getOneBySlug(params.movieSlug); | |
instance.assert(movie, 404); | |
return movie; | |
} | |
}); | |
instance.route({ | |
method: 'GET', | |
url: '/', | |
async handler(req, res) { | |
return Movie.listAll(); | |
} | |
}); | |
instance.route({ | |
method: 'DELETE', | |
url: '/:movieSlug', | |
async handler(req, res) { | |
const slug = req.params.movieSlug; | |
const movie = await Movie.getOneBySlug(slug); | |
instance.assert(movie, 404); | |
await Movie.delete(slug); | |
res.statusCode = 204; | |
} | |
}); | |
instance.route({ | |
method: 'POST', | |
url: '/', | |
schema: { | |
body: { | |
type: 'object', | |
properties: { | |
title: { | |
type: 'string' | |
}, | |
description: { | |
type: 'string' | |
} | |
}, | |
required: ['title'] | |
} | |
}, | |
async handler(req, res) { | |
const movie = await Movie.getOneByTitle(req.body.title); | |
instance.assert(!movie, 409, 'movie already exists'); | |
return Movie.create(req.body); | |
} | |
}); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default { | |
db: { | |
uri: 'mongodb://localhost:27017/movies' | |
}, | |
server: { | |
port: 9001 | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import slug from 'slug'; | |
import mongoose from 'mongoose'; | |
export const MovieSchema = new mongoose.Schema({ | |
title: { | |
type: String, | |
required: true | |
}, | |
slug: { | |
type: String, | |
unique: true, | |
required: true, | |
default: function () { | |
return slug(this.title); | |
} | |
}, | |
likes: { | |
type: Number, | |
default: 0 | |
}, | |
description: { | |
type: String | |
}, | |
isDeleted: { | |
type: Boolean, | |
default: false | |
} | |
}, { | |
versionKey: false | |
}); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fp from 'fastify-plugin' | |
import mongoose from 'mongoose'; | |
import {MovieSchema} from './db-models.js'; | |
export const createDBPlugin = ({db = mongoose } = {}) => fp(async (intance, opts) =>{ | |
await db.connect(opts.db.uri); | |
intance | |
.decorate('Movie', db.model('Movie', MovieSchema)) | |
.addHook('onClose', () =>db.close()); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
POST http://localhost:9001/movies | |
Content-Type: application/json | |
{ | |
"title": "Forest Gump", | |
"description": "foo bar bim" | |
} | |
### | |
DELETE http://localhost:9001/movies/forest-gump |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fastify from 'fastify'; | |
import fastifySensible from 'fastify-sensible'; | |
import mongoose from 'mongoose'; | |
import {createDBPlugin} from './db-plugin.js'; | |
import conf from './conf.js'; | |
import api from './api.js'; | |
const db = new mongoose.Mongoose(); | |
const app = fastify({ | |
logger: true | |
}); | |
app.register(fastifySensible); | |
// ... eventually other middleware etc | |
app.register(createDBPlugin({db}), conf); | |
app.register(api, {...conf, prefix:'/movies'}) | |
app.listen(conf.server.port); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const createService = ({model}) => { | |
return { | |
getOneBySlug(slug) { | |
return model.findOne({ | |
slug, | |
isDeleted: {$ne: true} | |
}).select({ | |
isDeleted: 0 | |
}); | |
}, | |
getOneByTitle(title) { | |
return model.findOne({ | |
title, | |
isDeleted: {$ne: true} | |
}).select({ | |
isDeleted: 0 | |
}); | |
}, | |
listAll() { | |
return model.find({ | |
isDeleted: {$ne: true} | |
}).select({ | |
isDeleted: 0 | |
}); | |
}, | |
async create(movie) { | |
return new model(movie).save(); | |
}, | |
async delete(slug) { | |
await model.findOneAndUpdate({ | |
slug | |
}, { | |
$set: { | |
isDeleted: true | |
} | |
}); | |
} | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "separation-of-concerns", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"type": "module", | |
"scripts": { | |
"test": "pta *.test.js", | |
"dev": "nodemon movie-store/index.js" | |
}, | |
"author": "", | |
"license": "ISC", | |
"dependencies": { | |
"fastify": "~3.14.1", | |
"fastify-sensible": "~3.1.1", | |
"mongoose": "~5.12.3", | |
"slug": "~4.0.3" | |
}, | |
"devDependencies": { | |
"fastify-plugin": "~3.0.0", | |
"nodemon": "~2.0.7", | |
"pta": "~0.2.2", | |
"sbuts": "~0.4.1" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fastify from 'fastify'; | |
import fastifySensible from 'fastify-sensible'; | |
import {routesPlugin} from './api.js'; | |
import stub from 'sbuts'; | |
export default (t) => { | |
const createTestApp = ({getOneBySlug}) => { | |
const app = fastify(); | |
app.register(fastifySensible); | |
// inject mock | |
app.decorate('Movie', { | |
getOneBySlug | |
}); | |
app.register(routesPlugin); | |
return app; | |
}; | |
t.test(`GET /:movieSlug should return 200 with the matching movie`, async (t) => { | |
const forestGump = { | |
likes: 0, | |
_id: '6071d76a0d5f31cb3e7e8a31', | |
title: 'Forest Gump', | |
description: 'foo bar bim', | |
slug: 'forest-gump' | |
}; | |
const getOneBySlug = stub().resolve(forestGump); | |
const app = createTestApp({getOneBySlug}); | |
const response = await app.inject({ | |
method: 'GET', | |
url: '/forest-gump' | |
}); | |
t.eq(response.statusCode, 200); | |
const json = await response.json(); | |
t.eq(json, forestGump); | |
t.eq(getOneBySlug.calls, [['forest-gump']]) | |
}); | |
t.test(`GET /:movieSlug should return 404 if no movie matches`, async (t) => { | |
const getOneBySlug = stub().resolve(null); | |
const app = createTestApp({getOneBySlug}); | |
const response = await app.inject({ | |
method: 'GET', | |
url: '/forest-gump' | |
}); | |
t.eq(response.statusCode, 404); | |
t.eq(getOneBySlug.calls, [['forest-gump']]) | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment