Skip to content

Commit

Permalink
GET /ideas?filter[highlyRated]=bottomRateLimit
Browse files Browse the repository at this point in the history
	* create GET /ideas?filter[highlyRated]=bottomRateLimit

	* renaming rate => voteSum, putting voteSum to meta in response

	* rename + fix in query (we were ignoring ideas with 0 votes before)
  • Loading branch information
agatatalita committed Apr 5, 2018
1 parent 3273c00 commit c83d42f
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 5 deletions.
1 change: 1 addition & 0 deletions apidoc.raml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ types:
- new ideas: `?sort=-created`
- ideas with provided creators: `?filter[creators]=username0,username1,username2`
- ideas commented by provided users: `?filter[commentedBy]=username0,username1,username2`
- highly voted ideas with minimum amount of votes parameter: `?filter[highlyVoted]=bottomValueLimit`
/{id}:
get:
description: Read an idea by id.
Expand Down
3 changes: 2 additions & 1 deletion controllers/goto/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
new: route(['query.sort'], 'newQuery'),
random: route(['query.filter.random']),
withCreators: route(['query.filter.creators']),
commentedBy: route(['query.filter.commentedBy'])
commentedBy: route(['query.filter.commentedBy']),
highlyVoted: route(['query.filter.highlyVoted'])
},
};
25 changes: 24 additions & 1 deletion controllers/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,5 +231,28 @@ async function getIdeasCommentedBy(req, res, next) {
}
}

/**
* Get highly voted ideas with an optional parameter of minimum votes
*/
async function getIdeasHighlyVoted(req, res, next) {
try {
// gather data
const { page: { offset = 0, limit = 5 } = { } } = req.query;
const { highlyVoted } = req.query.filter;

// read ideas from database
const foundIdeas = await models.idea.findHighlyVoted(highlyVoted, { offset, limit });

// serialize
const serializedIdeas = serialize.idea(foundIdeas);

// respond
return res.status(200).json(serializedIdeas);

} catch (e) {
return next(e);
}
}


module.exports = { get, getIdeasCommentedBy, getIdeasWithCreators, getIdeasWithMyTags, getIdeasWithTags, getNewIdeas, getRandomIdeas, patch, post };
module.exports = { get, getIdeasCommentedBy, getIdeasHighlyVoted, getIdeasWithCreators, getIdeasWithMyTags, getIdeasWithTags, getNewIdeas, getRandomIdeas, patch, post };
1 change: 1 addition & 0 deletions controllers/validators/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const validate = require('./validate-by-schema');
module.exports = {
get: validate('getIdea'),
getIdeasCommentedBy: validate('getIdeasCommentedBy'),
getIdeasHighlyVoted: validate('getIdeasHighlyVoted'),
getIdeasWithCreators: validate('getIdeasWithCreators'),
getIdeasWithMyTags: validate('getIdeasWithMyTags'),
getIdeasWithTags: validate('getIdeasWithTags'),
Expand Down
3 changes: 2 additions & 1 deletion controllers/validators/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const parametersDictionary = {
relatedToTags: 'array',
size: 'int',
creators: 'array',
commentedBy: 'array'
commentedBy: 'array',
highlyVoted: 'int'
},
};

Expand Down
24 changes: 23 additions & 1 deletion controllers/validators/schema/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,26 @@ const getIdeasCommentedBy = {
required: ['query']
};

module.exports = { getIdea, getIdeasCommentedBy, getIdeasWithCreators, getIdeasWithMyTags, getIdeasWithTags, getNewIdeas, getRandomIdeas, patchIdea, postIdeas };
const getIdeasHighlyVoted = {
properties: {
query: {
properties: {
filter: {
properties: {
highlyVoted: {
type: 'number'
}
},
required: ['highlyVoted'],
additionalProperties: false
},
page
},
required: ['filter'],
additionalProperties: false
},
},
required: ['query']
};

module.exports = { getIdea, getIdeasCommentedBy, getIdeasHighlyVoted, getIdeasWithCreators, getIdeasWithMyTags, getIdeasWithTags, getNewIdeas, getRandomIdeas, patchIdea, postIdeas };
31 changes: 31 additions & 0 deletions models/idea/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,37 @@ class Idea extends Model {
const cursor = await this.db.query(query, params);
return await cursor.all();
}


/**
* Read ideas commented by specified users
* @param {string[]} voteSumBottomLimit - minimal query voteSum
* @param {integer} offset - pagination offset
* @param {integer} limit - pagination limit
* @returns {Promise<Idea[]>} - list of found ideas
*/
static async findHighlyVoted(voteSumBottomLimit, { offset, limit }) {
const query = `
FOR idea IN ideas
LET ideaVotes = (FOR vote IN votes FILTER idea._id == vote._to RETURN vote)
// get sum of each idea's votes values
LET voteSum = SUM(ideaVotes[*].value)
// set bottom limit of voteSum
FILTER voteSum >= @voteSumBottomLimit
// find creator
LET c = (DOCUMENT(idea.creator))
LET creator = MERGE(KEEP(c, 'username'), c.profile)
LET ideaOut = MERGE(KEEP(idea, 'title', 'detail', 'created'), { id: idea._key}, { creator }, { voteSum })
// sort by amount of votes
SORT ideaOut.voteSum DESC, ideaOut.created DESC
LIMIT @offset, @limit
RETURN ideaOut`;

const params = { voteSumBottomLimit, offset, limit };
const cursor = await this.db.query(query, params);
return await cursor.all();
}
}

module.exports = Idea;
5 changes: 5 additions & 0 deletions routes/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ router.route('/')
router.route('/')
.get(go.get.commentedBy, authorize.onlyLogged, parse, ideaValidators.getIdeasCommentedBy, ideaControllers.getIdeasCommentedBy);

// get ideas commented by specified users
router.route('/')
.get(go.get.highlyVoted, authorize.onlyLogged, parse, ideaValidators.getIdeasHighlyVoted, ideaControllers.getIdeasHighlyVoted);



router.route('/:id')
// read idea by id
Expand Down
4 changes: 3 additions & 1 deletion serializers/ideas.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ const ideaSerializer = new Serializer('ideas', {
myVote(record, current) {
if (!current.hasOwnProperty('myVote')) return;
return (current.myVote) ? current.myVote.value : 0;
},
voteSum(record, current) {
return current.voteSum;
}
}

});

function idea(data) {
Expand Down
146 changes: 146 additions & 0 deletions test/ideas.list.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,150 @@ describe('read lists of ideas', () => {
});
});
});

describe('GET /ideas?filter[highlyVoted]', () => {
let user0;
// create and save testing data
beforeEach(async () => {
const primarys = 'ideas';
const data = {
users: 6,
tags: 6,
verifiedUsers: [0, 1, 2, 3, 4],
ideas: [[{}, 0], [{}, 0],[{}, 1],[{}, 2],[{}, 2],[{}, 2],[{}, 3]],
userTag: [
[0,0,'',5],[0,1,'',4],[0,2,'',3],[0,4,'',1],
[1,1,'',4],[1,3,'',2],
[2,5,'',2]
],
ideaTags: [
[0,0],[0,1],[0,2],
[1,1],[1,2],
[2,1],[2,2],[2,4],
[4,0],[4,1],[4,2],[4,3],[4,4],
[5,2],[5,3],
[6,3]
],
// odeas with votes: 3:3, 1:3, 5:1, 2:1, 0:0, 6: -1, 4:-2
votes: [
[0, [primarys, 0], -1],
[1, [primarys, 0], 1],
[0, [primarys, 1], 1],
[1, [primarys, 1], 1],
[2, [primarys, 1], 1],
[0, [primarys, 2], -1],
[1, [primarys, 2], 1],
[2, [primarys, 2], 1],
[0, [primarys, 3], 1],
[1, [primarys, 3], 1],
[2, [primarys, 3], 1],
[3, [primarys, 3], 1],
[4, [primarys, 3], -1],
[0, [primarys, 4], -1],
[1, [primarys, 4], -1],
[3, [primarys, 5], 1],
[3, [primarys, 6], -1]
]
};

dbData = await dbHandle.fill(data);

[user0, , , , , ] = dbData.users;
});

context('logged in', () => {

beforeEach(() => {
agent = agentFactory.logged(user0);
});

context('valid data', () => {

it('[highly voted ideas] 200 and return array of matched ideas', async () => {

// request
const response = await agent
.get('/ideas?filter[highlyVoted]=0')
.expect(200);

// without pagination, limit for ideas 5 we should find 5 ideas...
should(response.body).have.property('data').Array().length(5);

// sorted by creation date desc
should(response.body.data.map(idea => idea.attributes.title))
.eql([3, 1, 5, 2, 0].map(no => `idea title ${no}`));

});

it('[highly voted ideas with at least 2 votes in plus] 200 and return array of matched ideas', async () => {

// request
const response = await agent
.get('/ideas?filter[highlyVoted]=2')
.expect(200);

// without pagination, limit for ideas 5 we should find 5 ideas...
should(response.body).have.property('data').Array().length(2);

// sorted by creation date desc
should(response.body.data.map(idea => idea.attributes.title))
.eql([3, 1].map(no => `idea title ${no}`));

// shoud value be at least 2
should(Math.min(...response.body.data.map(idea => idea.meta.voteSum)))
.aboveOrEqual(2);
});


it('[pagination] offset and limit the results', async () => {
const response = await agent
.get('/ideas?filter[highlyVoted]=0&page[offset]=1&page[limit]=3')
.expect(200);

// we should find 3 ideas
should(response.body).have.property('data').Array().length(3);

// sorted by creation date desc
should(response.body.data.map(idea => idea.attributes.title))
.eql([1, 5, 2].map(no => `idea title ${no}`));
});

});

context('invalid data', () => {

it('[invalid query.filter.highlyVoted] 400', async () => {
await agent
.get('/ideas?filter[highlyVoted]=string')
.expect(400);
});

it('[invalid query.filter.highlyVoted] 400', async () => {
await agent
.get('/ideas?filter[highlyVoted]')
.expect(400);
});

it('[invalid pagination] 400', async () => {
await agent
.get('/ideas?filter[highlyVoted]=0&page[offset]=1&page[limit]=21')
.expect(400);
});

it('[unexpected query params] 400', async () => {
await agent
.get('/ideas?filter[highlyVoted]=0&additional[param]=3&page[offset]=1&page[limit]=3')
.expect(400);
});
});
});

context('not logged in', () => {
it('403', async () => {
await agent
.get('/ideas?filter[highlyVoted]=0')
.expect(403);
});
});
});
});

0 comments on commit c83d42f

Please sign in to comment.