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

WIP: watch ideas #60

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 app.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ app.use('/ideas', require('./routes/ideas'));
app.use('/ideas', require('./routes/votes'));
app.use('/comments', require('./routes/votes'));

// care about ideas
app.use('/ideas', require('./routes/cares'));

// following are route factories
// they need to know what is the primary object (i.e. idea, comment, etc.)
app.use('/ideas', require('./routes/primary-comments')('idea'));
Expand Down
13 changes: 13 additions & 0 deletions collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ module.exports = {
unique: true
}
]
},

cares: {
type: 'edge',
from: ['users'],
to: ['ideas'],
indexes: [
{
type: 'hash',
fields: ['_from', '_to'],
unique: true
}
]
}

};
Expand Down
86 changes: 86 additions & 0 deletions controllers/cares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const path = require('path'),
models = require(path.resolve('./models')),
serializers = require(path.resolve('./serializers'));

/**
* Middleware to add a care to idea.
*/
async function post(req, res, next) {
// read data from request
const { id } = req.params;
const { username } = req.auth;

const primarys = req.baseUrl.substring(1);
const primary = primarys.slice(0, -1);

try {
const care = await models.care.create({ from: username, to: { type: primarys, id } });

const serializedCare = serializers.serialize.care(care);
return res.status(201).json(serializedCare);
} catch (e) {
// handle errors
switch (e.code) {
// duplicate care
case 409: {
return res.status(409).json({
errors: [{
status: 409,
detail: 'duplicate care'
}]
});
}
// missing idea
case 404: {
return res.status(404).json({
errors: [{
status: 404,
detail: `${primary} doesn't exist`
}]
});
}
default: {
return next(e);
}
}
}
}

/**
* Middleware to DELETE a care from an idea (and other objects in the future).
*/
async function del(req, res, next) {

// read data from request
const { id } = req.params;
const { username } = req.auth;

// what is the type of the object we care for (i.e. ideas, comments, ...)
const primarys = req.baseUrl.substring(1);
const primary = primarys.slice(0, -1);

try {
// remove the care from database
await models.care.remove({ from: username, to: { type: primarys, id } });
// respond
return res.status(204).end();
} catch (e) {
// handle errors
switch (e.code) {
// primary object or care doesn't exist
case 404: {
return res.status(404).json({
errors: [{
status: 404,
detail: `care or ${primary} doesn't exist`
}]
});
}
default: {
return next(e);
}
}
}
}

module.exports = { del, post };
8 changes: 8 additions & 0 deletions controllers/validators/cares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

const validate = require('./validate-by-schema');

const del = validate('deleteCare');
const post = validate('postCares');

module.exports = { del, post };
1 change: 1 addition & 0 deletions controllers/validators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports.authenticate = require('./authenticate');
exports.avatar = require('./avatar');
exports.cares = require('./cares');
exports.comments = require('./comments');
exports.contacts = require('./contacts');
exports.messages = require('./messages');
Expand Down
30 changes: 30 additions & 0 deletions controllers/validators/schema/cares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const { id } = require('./paths');

const postCares = {
properties: {
params: {
properties: { id },
required: ['id'],
additionalProperties: false
},
body: {
additionalProperties: false
},
required: ['body', 'params']
}
};

const deleteCare = {
properties: {
params: {
properties: { id },
required: ['id'],
additionalProperties: false
},
required: ['params']
}
};

module.exports = { deleteCare, postCares };
3 changes: 2 additions & 1 deletion controllers/validators/schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const account = require('./account'),
authenticate = require('./authenticate'),
avatar = require('./avatar'),
cares = require('./cares'),
comments = require('./comments'),
contacts = require('./contacts'),
definitions = require('./definitions'),
Expand All @@ -16,5 +17,5 @@ const account = require('./account'),
votes = require('./votes');


module.exports = Object.assign({ definitions }, account, authenticate, avatar,
module.exports = Object.assign({ definitions }, account, authenticate, avatar, cares,
comments, contacts, ideas, ideaTags, messages, params, tags, users, userTags, votes);
94 changes: 94 additions & 0 deletions models/care/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict';

const path = require('path');

const Model = require(path.resolve('./models/model')),
schema = require('./schema');

class Watch extends Model {

/**
* Create a care.
* @param {string} from - username of the care giver
* @param {object} to - receiver object of the care
* @param {string} to.type - type of the receiver (collection name, i.e. 'ideas')
* @param {string} to.id - id of the receiver
* @returns Promise - the saved care
*/
static async create({ from: username, to: { type, id } }) {
// generate the care
const care = schema({ });

const query = `
FOR u IN users FILTER u.username == @username
FOR i IN @@type FILTER i._key == @id
LET care = MERGE(@care, { _from: u._id, _to: i._id })
INSERT care INTO cares

LET from = MERGE(KEEP(u, 'username'), u.profile)
LET to = MERGE(KEEP(i, 'title', 'detail', 'created'), { id: i._key })
LET savedWatch = MERGE(KEEP(NEW, 'created'), { id: NEW._key }, { from }, { to })
RETURN savedWatch`;
const params = { username, '@type': type, id, care };
const cursor = await this.db.query(query, params);
const out = await cursor.all();

// when nothing was created, throw error
if (out.length === 0) {
const e = new Error('not found');
e.code = 404;
throw e;
}

// what is the type of the object the care was given to (important for serialization)
out[0].to.type = type;

return out[0];
}

/**
* Read a care from user to idea or something.
* @param {string} from - username of the care giver
* @param {object} to - receiver object of the care
* @param {string} to.type - type of the receiver (collection name, i.e. 'ideas')
* @param {string} to.id - id of the receiver
* @returns Promise - the found care or undefined
*/
static async read({ from: username, to: { type, id } }) {
const query = `
FOR u IN users FILTER u.username == @username
FOR i IN @@type FILTER i._key == @id
FOR w IN cares FILTER w._from == u._id && w._to == i._id
RETURN w`;
const params = { username, '@type': type, id };
const cursor = await this.db.query(query, params);

return (await cursor.all())[0];
}

/**
* Remove a care.
* @param {string} from - username of the care giver
* @param {object} to - receiver object of the care
* @param {string} to.type - type of the receiver (collection name, i.e. 'ideas')
* @param {string} to.id - id of the receiver
* @returns Promise
*/
static async remove({ from: username, to: { type, id } }) {
const query = `
FOR u IN users FILTER u.username == @username
FOR i IN @@type FILTER i._key == @id
FOR v IN cares FILTER v._from == u._id AND v._to == i._id
REMOVE v IN cares`;
const params = { username, '@type': type, id };
const cursor = await this.db.query(query, params);

if (cursor.extra.stats.writesExecuted === 0) {
const e = new Error('primary or care not found');
e.code = 404;
throw e;
}
}
}

module.exports = Watch;
5 changes: 5 additions & 0 deletions models/care/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = function ({ created = Date.now() }) {
return { created };
};
5 changes: 3 additions & 2 deletions models/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const comment = require('./comment'),
const care = require('./care'),
comment = require('./comment'),
contact = require('./contact'),
idea = require('./idea'),
ideaTag = require('./idea-tag'),
Expand All @@ -22,4 +23,4 @@ const models = {
}
};

module.exports = Object.assign(models, { comment, contact, idea, ideaTag, message, model, tag, user, userTag, vote });
module.exports = Object.assign(models, { care, comment, contact, idea, ideaTag, message, model, tag, user, userTag, vote });
18 changes: 18 additions & 0 deletions routes/cares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const express = require('express'),
path = require('path');

const authorize = require(path.resolve('./controllers/authorize')),
careControllers = require(path.resolve('./controllers/cares')),
careValidators = require(path.resolve('./controllers/validators/cares'));

const router = express.Router();

router.route('/:id/cares')
.post(authorize.onlyLogged, careValidators.post, careControllers.post);

router.route('/:id/cares/care')
.delete(authorize.onlyLogged, careValidators.del, careControllers.del);

module.exports = router;
27 changes: 27 additions & 0 deletions serializers/cares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

const Serializer = require('jsonapi-serializer').Serializer;

const voteSerializer = new Serializer('cares', {
id: 'id',
attributes: ['created', 'from', 'to'],
keyForAttribute: 'camelCase',
typeForAttribute(attribute, doc) {
if (attribute === 'from') return 'users';
if (attribute === 'to') return doc.type;
},
from: {
ref: 'username',
type: 'users'
},
to: {
ref: 'id',
type: 'ideas'
}
});

function care(data) {
return voteSerializer.serialize(data);
}

module.exports = { care };
5 changes: 3 additions & 2 deletions serializers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

const Deserializer = require('jsonapi-serializer').Deserializer;

const comments = require('./comments'),
const cares = require('./cares'),
comments = require('./comments'),
contacts = require('./contacts'),
ideas = require('./ideas'),
ideaTags = require('./idea-tags'),
Expand Down Expand Up @@ -44,6 +45,6 @@ function deserialize(req, res, next) {
}

module.exports = {
serialize: Object.assign({ }, comments, contacts, ideas, ideaTags, messages, tags, users, votes),
serialize: Object.assign({ }, cares, comments, contacts, ideas, ideaTags, messages, tags, users, votes),
deserialize
};
2 changes: 1 addition & 1 deletion serializers/votes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Serializer = require('jsonapi-serializer').Serializer;

const voteSerializer = new Serializer('votes', {
id: 'id',
attributes: ['title', 'detail', 'created', 'value', 'from', 'to'],
attributes: ['created', 'value', 'from', 'to'],
keyForAttribute: 'camelCase',
typeForAttribute(attribute, doc) {
if (attribute === 'from') return 'users';
Expand Down
Loading