Skip to content

Commit 0770dd4

Browse files
committed
Tags: Initial commit.
Tags can now be added to projects. Tags consist of a "Tag Entity", which has the actual definition of the tag such as { type, name }. Tags themselves are just mappings between the Tag entities and the projects or tasks. A tag looks like this: { projectId: x, tagId: y } Tag Entities can only be manipulated by admins using the standard CRUD functions at the /tagentity end point. Tags can be manipulated by anyone that owns that project, at the /tag end point. Important endpoints: /tag/* -- standard CRUD endpoints /tag/add -- adds a new tag entity /tag -- disabled so you can't list all tag mappings /tag/findAllByProjectId/:id -- list all the tags for a given projectId This end point will also look up and return the tag entities. An initial set of test cases is included.
1 parent b2a0b51 commit 0770dd4

File tree

9 files changed

+371
-5
lines changed

9 files changed

+371
-5
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "assets/js/vendor/select2"]
2+
path = assets/js/vendor/select2
3+
url = https://github.com/ivaynberg/select2.git

api/controllers/EventController.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
var _ = require('underscore');
9+
var async = require('async');
910
var icalendar = require('icalendar');
1011
var projUtils = require('../services/projectUtils');
1112

api/controllers/TagController.js

+91
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,97 @@
55
* @description :: Manipulate tags (add / delete / assign to projects/tasks)
66
*/
77

8+
var _ = require('underscore');
9+
var async = require('async');
10+
var projUtils = require('../services/projectUtils');
11+
812
module.exports = {
913

14+
findAllByProjectId: function (req, res) {
15+
Tag.findByProjectId(req.params.id, function (err, tags) {
16+
var entities = {};
17+
var tagIds = [];
18+
19+
// Helper function for async to look up each tag entitiy
20+
var getTags = function (tagId, done) {
21+
if (entities[tagId]) { return done(); }
22+
TagEntity.findOne(tagId, function (err, t) {
23+
entities[tagId] = t;
24+
done(err);
25+
});
26+
}
27+
28+
// Get all the tag ids
29+
for (var i = 0; i < tags.length; i++) {
30+
tagIds.push(tags[i].id);
31+
}
32+
33+
// Get the tag entities for each id
34+
async.each(tagIds, getTags, function(err) {
35+
if (err) { return res.send(400, { message: "Error looking up tags"}); }
36+
// Attach the tag entity to the tag
37+
for (var i = 0; i < tags.length; i++) {
38+
tags[i].tag = entities[tags[i].id];
39+
return res.send(tags);
40+
}
41+
});
42+
});
43+
},
44+
45+
// Add a new tag entity to the system (alias for TagEntity.create)
46+
add: function (req, res) {
47+
if (req.route.method != 'post') { return res.send(400, { message: 'Unsupported operation.' } ); }
48+
var tag = _.extend(req.body || {}, req.params);
49+
TagEntity.findOne({where: { name: tag.name, type: tag.type }}, function (err, existingTag) {
50+
if (err) { return res.send(400, { message: 'Error looking up tag' }); }
51+
if (existingTag) { return res.send(existingTag); }
52+
TagEntity.create(tag, function (err, tag) {
53+
if (err) { return res.send(400, { message: 'Error creating tag' }); }
54+
return res.send(tag);
55+
});
56+
});
57+
},
58+
59+
// Override default create to check parameters and
60+
// ensure duplicate tags are not created.
61+
create: function (req, res) {
62+
if (req.route.method != 'post') { return res.send(400, { message: 'Unsupported operation.' } ); }
63+
var tag = _.extend(req.body || {}, req.params);
64+
if (!tag.projectId) { tag.projectId = null; }
65+
if (!tag.taskId) { tag.taskId = null; }
66+
if (!tag.tagId) { return res.send(400, { message: "Must specify a tag id" }); }
67+
if (!tag.projectId && !tag.taskId) { return res.send(400, { message: "Must specify either a project or task" }); }
68+
// check if the tag already exists
69+
Tag.findOne(
70+
{ where: { projectId: tag.projectId, taskId: tag.taskId, tagId: tag.tagId }},
71+
function (err, existingTag) {
72+
if (err) { return res.send(400, { message: 'Error looking up tag' }); }
73+
if (existingTag) { return res.send(existingTag); }
74+
// Create tag if it doesn't exist
75+
Tag.create(tag, function (err, tag) {
76+
if (err) { return res.send(400, { message: 'Error creating tag' }); }
77+
return res.send(tag);
78+
});
79+
}
80+
);
81+
},
82+
83+
// Override destroy to ensure owner has access to project
84+
destroy: function (req, res) {
85+
if (req.route.method != 'delete') { return res.send(400, { message: 'Unsupported operation.' } ); }
86+
Tag.findOneById( req.params.id, function (err, tag) {
87+
if (err) { return res.send(400, { message: 'Error looking up tag' }); }
88+
if (!tag) { return res.send(404, { message: 'Tag not found'}); }
89+
// check if this user is authorized
90+
projUtils.authorized(tag.projectId, req.user[0].id, function (err, proj) {
91+
if (err) { return res.send(400, { message: err }); }
92+
if (!err && !proj) { return res.send(403, { message: 'Not authorized.'}); }
93+
tag.destroy(function (err) {
94+
if (err) { return res.send(400, { message: 'Error destroying tag mapping' }); }
95+
return res.send(tag);
96+
});
97+
});
98+
});
99+
}
100+
10101
};
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* TagEntityController
3+
*
4+
* @module :: Controller
5+
* @description :: Administration of the system's tags
6+
*/
7+
8+
module.exports = {
9+
10+
/**
11+
* This controller is purely for administrative use to
12+
* add/remove/list/etc tag entities.
13+
*
14+
* The security policies prevent non-Administrator users
15+
* from accessing this controller.
16+
*/
17+
18+
};

api/policies/projectId.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Get the project referenced in :id and check if access is allowed
3+
*/
4+
var util = require('../services/projectUtils')
5+
6+
module.exports = function projectId (req, res, next) {
7+
if (req.body) {
8+
var userId = null;
9+
if (req.user) {
10+
userId = req.user[0].id;
11+
}
12+
util.authorized(req.body.projectId, userId, function (err, proj) {
13+
if (err) { return res.send({ message: err }); }
14+
if (!err && !proj) { return res.send(403, { message: 'Not authorized.'}); }
15+
req.proj = proj;
16+
req.projectId = proj.id;
17+
next();
18+
});
19+
// no :id is specified, so continue
20+
} else {
21+
next();
22+
}
23+
};

assets/js/vendor/select2

Submodule select2 added at e78cf77

config/policies.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,31 @@ module.exports.policies = {
5959
'unlike': ['authenticated', 'requireUserId', 'addUserId', 'requireId']
6060
},
6161

62-
TaskController : {
63-
'findAllByProjectId': ['authenticated', 'addUserId', 'project']
64-
},
65-
6662
EventController : {
6763
'create': ['authenticated', 'addUserId', 'eventUuid'],
6864
'findAllByProjectId': ['authenticated', 'addUserId', 'requireId', 'project'],
6965
'attend': ['authenticated', 'requireUserId', 'addUserId', 'requireId'],
7066
'cancel': ['authenticated', 'requireUserId', 'addUserId', 'requireId'],
7167
'rsvp': ['authenticated', 'requireUserId', 'addUserId'],
7268
'ical': ['authenticated', 'addUserId', 'project']
69+
},
70+
71+
TagController : {
72+
'*': ['authenticated'],
73+
'find': false,
74+
'create': ['authenticated', 'requireUserId', 'projectId'],
75+
'destroy': ['authenticated', 'requireUserId', 'requireId'],
76+
'add': ['authenticated', 'requireUserId'],
77+
'findAllByProjectId': ['authenticated', 'requireId', 'project']
78+
},
79+
80+
TagEntityController : {
81+
// Purely for administrative functions
82+
'*': 'admin'
83+
},
84+
85+
TaskController : {
86+
'findAllByProjectId': ['authenticated', 'addUserId', 'project']
7387
}
7488

7589
/*

test/api/sails/helpers/config.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,19 @@ module.exports = {
1313
'project': {
1414
'title': 'Project Title',
1515
'description': 'Project Description'
16-
}
16+
},
17+
'tags': [
18+
{
19+
'type': 'skill',
20+
'name': 'Tag1'
21+
},
22+
{
23+
'type': 'skill',
24+
'name': 'Tag2'
25+
},
26+
{
27+
'type': 'office',
28+
'name': 'Tag3'
29+
}
30+
]
1731
};

0 commit comments

Comments
 (0)