Skip to content

Commit 1dee5ab

Browse files
update
1 parent 9765308 commit 1dee5ab

14 files changed

+196
-21
lines changed

container.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const getenv = require('getenv');
44
const dotenv = require('dotenv');
55
const awilix = require('awilix');
66
const application = require('./lib/application');
7+
const grayscaleProcessor = require('./lib/grayscale-processor');
78
const logger = require('./loggers/logger');
89
const mongo = require('./datasources/mongo');
910
const container = awilix.createContainer();
@@ -13,28 +14,37 @@ module.exports = container;
1314

1415
let opts = {
1516
formatName: 'camelCase',
16-
lifetime: awilix.Lifetime.SCOPED
17+
registrationOptions: {
18+
lifetime: awilix.Lifetime.SCOPED
19+
}
1720
};
1821

1922
container
2023
.loadModules([
2124
'controllers/*.js',
22-
'datasources/mongo/models/*.js',
2325
'datasources/mongo/repositories/*.js',
2426
'interface/http/routes/*.js'
2527
], opts);
2628

2729
container
2830
.registerClass({
2931
application: [application, { lifetime: awilix.Lifetime.SINGLETON }],
32+
grayscaleProcessor: [grayscaleProcessor, { lifetime: awilix.Lifetime.SINGLETON }]
3033
})
3134
.registerFunction({
32-
logger: [logger, { lifetime: awilix.Lifetime.SINGLETON }]
35+
logger: [logger, { lifetime: awilix.Lifetime.SINGLETON }],
36+
database: [mongo, { lifetime: awilix.Lifetime.SINGLETON }]
3337
})
38+
.loadModules(
39+
['datasources/mongo/models/*.js'], {
40+
formatName: 'camelCase',
41+
registrationOptions: {
42+
lifetime: awilix.Lifetime.TRANSIENT
43+
}
44+
})
3445
// TODO: need abstract storages, config classes
3546
.registerValue({
36-
configs: getenv,
37-
database: mongo
47+
configs: getenv
3848
});
3949

4050
// container

controllers/article-controller.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ const AbstractController = require('./abstract-controller');
33
const ARTICLES_COUNT = 25;
44

55
class ArticleController extends AbstractController {
6-
constructor({ logger, configs, articleModel }) {
6+
constructor({ logger, configs, database, articleModel }) {
7+
super();
78
this.logger = logger;
89
this.configs = configs;
9-
this.articleModel = articleModel;
10+
this.database = database;
11+
this.articleModel_ = articleModel;
12+
this.articleModel = database.model('Article');
1013
}
1114

1215
findAll(query, page) {
@@ -16,13 +19,16 @@ class ArticleController extends AbstractController {
1619
.articleModel
1720
.find(query)
1821
.skip((page - 1) * ARTICLES_COUNT)
19-
.limit(ARTICLES_COUNT);
22+
.populate('tags')
23+
.limit(ARTICLES_COUNT)
24+
.exec();
2025
}
2126

2227
findOne(query) {
2328
return this
2429
.articleModel
25-
.findOne(query);
30+
.findOne(query)
31+
.exec();
2632
}
2733

2834
create(entity) {
+52
Original file line numberDiff line numberDiff line change
@@ -1 +1,53 @@
11
'use strict';
2+
const sinon = require('sinon');
3+
const sinonMongoose = require('sinon-mongoose');
4+
const expect = require('chai')
5+
.expect;
6+
const mongoose = require('mongoose');
7+
8+
const ArticleModel = require('../datasources/mongo/models/article-model');
9+
const ArticleController = require('./article-controller');
10+
11+
describe('ArticleController', () => {
12+
let controller, loggerMock, configsMock, databaseMock, articleModelMock;
13+
14+
beforeEach(() => {
15+
loggerMock = {};
16+
configsMock = {};
17+
18+
articleModelMock = sinon.mock(ArticleModel);
19+
databaseMock = {
20+
model: () => ArticleModel
21+
};
22+
});
23+
24+
it('should find all', (done) => {
25+
// let spy = sinon.spy(articleModelMock, 'find');
26+
27+
articleModelMock
28+
.expects('find')
29+
// .withArgs()
30+
.chain('limit', 25)
31+
.chain('skip', 0)
32+
.chain('exec')
33+
.yields(null, []);
34+
35+
controller = new ArticleController({
36+
logger: loggerMock,
37+
configs: configsMock,
38+
database: databaseMock,
39+
articleModel: articleModelMock
40+
});
41+
42+
return controller
43+
.findAll({}, 1)
44+
.then(result => {
45+
articleModelMock.verify();
46+
articleModelMock.restore();
47+
expect(result)
48+
.to.exist;
49+
50+
done();
51+
});
52+
});
53+
});

controllers/image-controller.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const fs = require('fs');
3+
const path = require('path');
4+
const crypto = require('crypto');
5+
const AbstractController = require('./abstract-controller');
6+
7+
class ImageController extends AbstractController {
8+
constructor({ logger, configs, grayscaleProcessor }) {
9+
super();
10+
this.logger = logger;
11+
this.configs = configs;
12+
this.grayscaleProcessor = grayscaleProcessor;
13+
this.stream = null;
14+
}
15+
16+
store(stream) {
17+
return new Promise((resolve) => {
18+
let filename = crypto.randomBytes(8)
19+
.toString('hex') + '.' + path.extname(stream.path);
20+
let fullpath = path.resolve(__dirname, '../storage', filename),
21+
rstream = fs.createReadStream(path);
22+
23+
rstream.pipe(fs.createWriteStream(fullpath));
24+
rstream.end();
25+
26+
resolve(stream);
27+
});
28+
}
29+
30+
grayscale(stream) {
31+
return this.grayscaleProcessor.execute(stream);
32+
}
33+
}
34+
35+
module.exports = ImageController;

datasources/mongo/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
const mongoose = require('mongoose');
33

4-
module.exports = (configs) => {
4+
module.exports = ({ configs }) => {
5+
mongoose.Promise = global.Promise;
56
return mongoose.connect(configs.string('MONGO_URI'));
67
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
class ArticleModelMock {
4+
5+
}
6+
7+
module.exports = ArticleModelMock;

datasources/mongo/models/article-model.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ const ArticleSchema = new mongoose.Schema({
2525
type: String,
2626
required: true,
2727
trim: true
28-
}
28+
},
29+
30+
tags: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ArticleTag' }]
2931
}, { minimize: false });
3032

3133
ArticleSchema.plugin(createdModifiedPlugin, { index: true });
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const mongoose = require('mongoose'),
4-
createdModifiedPlugin = require('mongoose-createdmodified')
4+
plugin = require('mongoose-createdmodified')
55
.createdModifiedPlugin;
66

77
const ArticleTagSchema = new mongoose.Schema({
@@ -11,15 +11,15 @@ const ArticleTagSchema = new mongoose.Schema({
1111
trim: true
1212
},
1313

14-
imageUrl: {
14+
avatarUrl: {
1515
type: String,
1616
required: true,
1717
trim: true
1818
}
1919

2020
}, { minimize: false });
2121

22-
ArticleTagSchema.plugin(createdModifiedPlugin, { index: true });
22+
ArticleTagSchema.plugin(plugin, { index: true });
2323
const ArticleTag = mongoose.model('ArticleTag', ArticleTagSchema);
2424

2525
module.exports = ArticleTag;

interface/http/index.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const path = require('path');
23
const restify = require('restify');
34
const container = require('../../container');
45
const application = container.resolve('application');
@@ -13,9 +14,11 @@ let server = restify.createServer({
1314

1415
server.use(restify.acceptParser(server.acceptable));
1516
server.use(restify.queryParser());
17+
server.use(restify.bodyParser({
18+
mapFiles: true
19+
}));
1620
server.use(restify.CORS());
1721
server.use(restify.gzipResponse());
18-
server.use(restify.bodyParser());
1922

2023
server.get('/articles', container.cradle.articleRoute.list);
2124
server.get('/articles/:id', container.cradle.articleRoute.view);
@@ -29,12 +32,14 @@ server.post('/tags/:id', container.cradle.tagRoute.create);
2932
server.put('/tags/:id', container.cradle.tagRoute.update);
3033
server.del('/tags/:id', container.cradle.tagRoute.delete);
3134

32-
application
35+
module.exports = application
3336
.start()
3437
.then(() => {
35-
return server.listen(port, () => {
38+
server.listen(port, () => {
3639
logger.info('%s listening at %s', server.name, server.url);
3740
});
41+
42+
return server;
3843
})
3944
.catch(cause => {
4045
logger.error(cause.message, cause.stack);

interface/http/routes/article-route.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
2+
const fs = require('fs');
23

3-
module.exports = ({ articleController }) => {
4+
module.exports = ({ articleController, imageController }) => {
45
let route = {};
56

67
route.list = (req, res, next) => {
@@ -24,8 +25,17 @@ module.exports = ({ articleController }) => {
2425
};
2526

2627
route.create = (req, res, next) => {
27-
return articleController
28-
.create(req.body)
28+
return imageController
29+
.store(fs.createReadStream(req.files.image.path))
30+
.then(stream => {
31+
let attrs = req.body;
32+
attrs.avatar_url = stream.path;
33+
34+
return Promise.all([
35+
articleController.create(attrs),
36+
imageController.grayscale(stream)
37+
]);
38+
})
2939
.then(result => res.json(result))
3040
.catch(cause => {
3141
return next(cause);

interface/http/tests/test.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
const supertest = require('supertest');
3+
const server = require('../');
4+
5+
6+
describe('Routes testing...', function () {
7+
let httpEndpoint = supertest.agent("http://localhost:4000");
8+
9+
it('should work', function (done) {
10+
httpEndpoint
11+
.get('/articles')
12+
.expect('Content-Type', /json/)
13+
.expect('Content-Length', '60')
14+
.expect(200)
15+
.end((err, res) => {
16+
17+
console.log(res);
18+
done();
19+
});
20+
});
21+
22+
23+
});

lib/grayscale-processor.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
const grayscale = require('image-grayscale');
3+
4+
class GrayscaleProcessor {
5+
constructor() {
6+
7+
}
8+
9+
execute(stream) {
10+
return grayscale(stream, { logProgress: true });
11+
}
12+
13+
}
14+
15+
module.exports = GrayscaleProcessor;

lib/local-storage.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
class LocalFileStorage {
4+
constructor() {
5+
6+
}
7+
}

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Samlple node-blog ",
55
"scripts": {
66
"start": "node interface/http",
7-
"test": "exit;"
7+
"test": "mocha --reporter spec --check-leaks"
88
},
99
"repository": {
1010
"type": "git",
@@ -45,6 +45,8 @@
4545
"chai": "^3.5.0",
4646
"mocha": "^3.3.0",
4747
"sinon": "^2.1.0",
48+
"sinon-as-promised": "^4.0.3",
49+
"sinon-mongoose": "^2.0.1",
4850
"supertest": "^3.0.0"
4951
}
5052
}

0 commit comments

Comments
 (0)