lfapi

view lfapi/main.js @ 26:73670695f8c4

member.activated is a timestamp not a boolean
author bsw
date Sun Nov 06 19:57:55 2011 +0100 (2011-11-06)
parents 6bda0d267f08
children a37c91ce44d6
line source
1 var api_version = '0.2.0';
3 // creates a random string with the given length
4 function randomString(number_of_chars) {
5 var charset, rand, i, ret;
6 charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7 random_string = '';
9 for (var i = 0; i < number_of_chars; i++) {
10 random_string += charset[parseInt(Math.random() * charset.length)]
11 }
12 return random_string;
13 }
15 var fields = require('./fields.js');
17 var general_params = require('./general_params.js');
19 var config = general_params.config;
20 exports.config = config;
22 var db = require('./db.js');
23 exports.db = db;
25 var selector = db.selector;
27 var nodemailer = require('nodemailer');
30 // check if current session has at least given access level, returns error to client if not.
31 // used by request handlers below
32 function requireAccessLevel(conn, req, res, access_level, callback) {
33 switch (access_level) {
34 case 'anonymous':
35 if (req.current_access_level == 'anonymous') { callback(); return; };
36 case 'pseudonym':
37 if (req.current_access_level == 'pseudonym') { callback(); return; };
38 case 'full':
39 if (req.current_access_level == 'full') { callback(); return; };
40 case 'member':
41 if (req.current_member_id) { callback(); return; };
42 default:
43 respond('json', conn, req, res, 'forbidden', { error: 'Access denied' });
44 }
45 };
47 // callback function, encoding result and sending it to the client
48 function respond(mode, conn, req, res, status, object, err) {
49 var http_status = 200;
50 var command;
52 if (status == 'ok') {
53 command = 'COMMIT';
54 } else {
55 command = 'ROLLBACK';
56 };
58 switch (status) {
59 case 'ok':
60 http_status = 200;
61 break;
62 case 'forbidden':
63 //http_status = 403;
64 break;
65 case 'notfound':
66 http_status = 404;
67 break;
68 case 'unprocessable':
69 //http_status = 422;
70 break;
71 case 'conflict':
72 //http_status = 409;
73 break;
74 };
76 var query;
77 if (mode == 'json' && ! err) query = 'SELECT null';
78 db.query(conn, req, res, query, function(result, conn) {
79 db.query(conn, req, res, command, function (result, conn) {
81 if (mode == 'json') {
82 if (! object) object = {};
83 } else if (mode == 'html') {
84 if (! object) object = 'no content';
85 if (err) object = "Error: " + err;
86 }
88 object.status = status;
89 object.error = err;
91 if (mode == 'json') {
92 var body = JSON.stringify(object);
93 var content_type = 'application/json; charset=UTF-8';
94 if (req.params && req.params.callback) {
95 body = req.params.callback + '(' + body + ')';
96 content_type = 'text/javascript; charset=UTF-8';
97 }
98 res.writeHead(
99 http_status,
100 {
101 'Content-Type': content_type,
102 //'Content-Length': body.length // TODO doesn't work in chrome with JSONP
103 }
104 );
105 res.end(body);
106 } else if (mode == 'html') {
107 var body = ['<html><head><title>lfapi</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style>body { font-family: sans-serif; }</style></head><body>']
108 body.push(object)
109 body.push('</body></html>')
110 body = body.join('');
111 res.writeHead(
112 http_status,
113 {
114 'Content-Type': 'text/html; charset=UTF-8',
115 'Content-Length': body.length
116 }
117 );
118 res.end(body);
119 }
120 })
121 });
122 };
124 exports.respond = respond;
125 db.error_handler = respond;
127 // add requested related data for requests with include_* parameters
128 function addRelatedData(conn, req, res, result, includes) {
129 if (includes.length > 0) {
130 var include = includes.shift();
131 var class = include.class;
132 var objects = result[include.objects];
134 var query;
136 if (objects) {
137 var objects_exists = false;
138 var ids_hash = {};
139 if (typeof(objects) == 'array') {
140 if (objects.length > 0) {
141 objects_exists = true;
142 objects.forEach( function(object) {
143 if (object[class + "_id"]) {
144 ids_hash[object[class + "_id"]] = true;
145 };
146 });
147 }
148 } else {
149 for (var key in objects) {
150 objects_exists = true;
151 var object = objects[key];
152 if (object[class + "_id"]) {
153 ids_hash[object[class + "_id"]] = true;
154 };
155 };
156 };
158 if (objects_exists) {
159 var ids = [];
160 for (key in ids_hash) {
161 ids.push(key)
162 }
163 if (ids.length > 0) {
164 query = new selector.Selector();
165 query.from(class);
166 query.addWhere([class + '.id IN (??)', ids]);
167 fields.addObjectFields(query, class);
168 }
169 };
170 };
172 db.query(conn, req, res, query, function (result2, conn) {
173 // add result to main result, regarding correct pluralization
174 var tmp = {};
175 if (result2) {
176 result2.rows.forEach( function(row) {
177 tmp[row.id] = row;
178 });
179 };
181 if (class == 'policy') {
182 result['policies'] = tmp;
183 } else {
184 result[class + 's'] = tmp;
185 }
186 addRelatedData(conn, req, res, result, includes);
187 });
188 } else {
189 respond('json', conn, req, res, 'ok', result);
190 };
192 };
194 function lockMemberById(conn, req, res, member_id, callback) {
195 var query = new selector.Selector('member');
196 query.addField('NULL');
197 query.addWhere(['member.id = ?', member_id]);
198 query.forUpdate();
199 db.query(conn, req, res, query, callback);
200 };
202 function requireUnitPrivilege(conn, req, res, unit_id, callback) {
203 var query = new selector.Selector('privilege');
204 query.addField('NULL');
205 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
206 query.addWhere(['privilege.unit_id = ?', unit_id ]);
207 query.addWhere('privilege.voting_right');
208 query.forShareOf('privilege');
209 db.query(conn, req, res, query, function(result, conn) {
210 if (result.rows.length != 1) {
211 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for this unit.');
212 return;
213 }
214 callback();
215 });
216 };
218 function requireAreaPrivilege(conn, req, res, area_id, callback) {
219 var query = new selector.Selector('privilege');
220 query.join('area', null, 'area.unit_id = privilege.unit_id');
221 query.addField('NULL');
222 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
223 query.addWhere(['area.id = ?', area_id ]);
224 query.addWhere('privilege.voting_right');
225 query.forShareOf('privilege');
226 db.query(conn, req, res, query, function(result, conn) {
227 if (result.rows.length != 1) {
228 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for areas in this unit.');
229 return;
230 }
231 callback();
232 });
233 };
235 function requireIssuePrivilege(conn, req, res, issue_id, callback) {
236 var query = new selector.Selector('privilege');
237 query.join('area', null, 'area.unit_id = privilege.unit_id');
238 query.join('issue', null, 'issue.area_id = area.id');
239 query.addField('NULL');
240 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
241 query.addWhere(['issue.id = ?', issue_id ]);
242 query.addWhere('privilege.voting_right');
243 query.forShareOf('privilege');
244 db.query(conn, req, res, query, function(result, conn) {
245 if (result.rows.length != 1) {
246 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for issues in this unit.');
247 return;
248 }
249 callback();
250 });
251 };
253 function requireInitiativePrivilege(conn, req, res, initiative_id, callback) {
254 var query = new selector.Selector('privilege');
255 query.join('area', null, 'area.unit_id = privilege.unit_id');
256 query.join('issue', null, 'issue.area_id = area.id');
257 query.join('initiative', null, 'initiative.issue_id = issue.id');
258 query.addField('NULL');
259 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
260 query.addWhere(['initiative.id = ?', initiative_id ]);
261 query.addWhere('privilege.voting_right');
262 query.forShareOf('privilege');
263 db.query(conn, req, res, query, function(result, conn) {
264 if (result.rows.length != 1) {
265 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for initiatives in this unit.');
266 return;
267 }
268 callback();
269 });
270 };
272 function requireIssueState(conn, req, res, issue_id, required_states, callback) {
273 var query = new selector.Selector('issue');
274 query.addField('NULL');
275 query.addWhere(['issue.id = ?', issue_id]);
276 query.addWhere(['issue.state IN (??)', required_states]);
277 query.forUpdateOf('issue');
278 db.query(conn, req, res, query, function(result, conn) {
279 if (result.rows.length != 1) {
280 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
281 return;
282 }
283 callback();
284 });
285 };
287 function requireIssueStateForInitiative(conn, req, res, initiative_id, required_states, callback) {
288 var query = new selector.Selector('issue');
289 query.join('initiative', null, 'initiative.issue_id = issue.id');
290 query.addField('NULL');
291 query.addWhere(['initiative.id = ?', initiative_id]);
292 query.addWhere(['issue.state IN (??)', required_states]);
293 query.forUpdateOf('issue');
294 db.query(conn, req, res, query, function(result, conn) {
295 if (result.rows.length != 1) {
296 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
297 return;
298 }
299 callback();
300 });
301 }
303 function requireContingentLeft(conn, req, res, is_initiative, callback) {
304 var query = new selector.Selector('member_contingent_left');
305 query.addField('NULL');
306 query.addWhere(['member_contingent_left.member_id = ?', req.current_member_id]);
307 query.addWhere('member_contingent_left.text_entries_left >= 1');
308 if (is_initiative) {
309 query.addWhere('member_contingent_left.initiatives_left >= 1');
310 }
311 db.query(conn, req, res, query, function(result, conn) {
312 if (result.rows.length != 1) {
313 respond('json', conn, req, res, 'forbidden', null, 'Contingent empty.');
314 return;
315 }
316 callback();
317 });
318 }
320 // ==========================================================================
321 // GET methods
322 // ==========================================================================
325 exports.get = {
327 // startpage (html) for users
328 // currently used for implementing public alpha test
329 '/': function (conn, req, res, params) {
331 var html = [];
332 html.push('<h2>welcome to lfapi public developer alpha test</h2>');
333 html.push('<p>This service is provided for testing purposes and is <i><b>dedicated to developers interested in creating applications</b></i> based on LiquidFeedback.</p>');
334 html.push('<h2>how to use</h2>');
335 html.push('<p>The programming interface is described in the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/API">LiquidFeedback API specification</a>.</p>')
336 html.push('<p>The current implementation status of lfapi is published at the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/lfapi">LiquidFeedback API server</a> page in our Wiki.</p>');
337 html.push('<p><b><i>Neither the API specification nor the implementation of lfapi is finished yet.</i></b> This public test should enable developers to join the specification process of the programming interface and makes it possible to start creating applications.</p>');
338 html.push('<h2>questions and suggestions</h2>');
339 html.push('<p>Please use our <a href="http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main">public mailing list</a> if you have any questions or suggestions.</p>');
340 html.push('<h2>developer registration</h2>');
341 html.push('<p>To register as developer and receive an account, please submit the following form. You\'ll receive an email with instructions to complete the registration process by verifying your email address.<br />');
342 html.push('<form action="register_test" method="POST">');
343 html.push('<label for="name">Your name:</label> <input type="text" id="name" name="name" /> &nbsp; &nbsp; ');
344 html.push('<label for="email">Email address:</label> <input type="text" id="email" name="email" /> &nbsp; &nbsp; ');
345 html.push('<label for="location">Location:</label> <select name="location" id="location"><option value="earth">Earth</option><option value="moon">Moon</option><option value="mars">Mars</option></select>');
346 html.push('<br />');
347 html.push('<br />');
348 html.push('<div style="border: 2px solid #c00000; background-color: #ffa0a0; padding: 1ex;">');
349 html.push('<b>WARNING:</b> All data you entered above and all data you enter later while using the system and all data you are submitting via the programming interface will be stored in the LiquidFeedback database and published. Every access to the system is subject of tracing and logging for development purposes.<br />Please notice, this is a <b>public alpha test dedicated to developers</b>: serious errors can happen, private data unintentionally published or even <a href="http://en.wikipedia.org/wiki/Grey_goo"> grey goo</a> can appear without further warning. Everything is <b>ON YOUR OWN RISK</b>!');
350 html.push('<br />');
351 html.push('<br />');
352 html.push('<input type="checkbox" name="understood" value="understood" /> I understand the previous warning and I understand that everything is on my own risk.<br />');
353 html.push('</div>');
354 html.push('<br />');
355 html.push('<input type="submit" value="Register account" />');
356 respond('html', null, req, res, 'ok', html.join(''));
357 },
359 // temporary method to implement public alpha test
360 '/register_test_confirm': function (conn, req, res, params) {
361 var secret = params.secret;
363 var query = new selector.Selector('member');
364 query.addField('member.id, member.notify_email_unconfirmed');
365 query.addWhere(['member.notify_email_secret = ?', secret]);
366 db.query(conn, req, res, query, function (result, conn) {
367 var member = result.rows[0];
368 if (member) {
369 var query = new selector.SQLUpdate('member');
370 query.addValues({
371 notify_email: member.notify_email_unconfirmed,
372 notify_email_secret: null,
373 notify_email_unconfirmed: null,
374 active: true,
375 activated: 'now',
376 active: true,
377 last_activity: 'now',
378 locked: false
379 });
380 query.addWhere(['id = ?', member.id]);
381 db.query(conn, req, res, query, function (err, result) {
382 respond('html', conn, req, res, 'ok', 'Account activated: ');
383 });
384 } else {
385 respond('html', conn, req, res, 'forbidden', 'Secret not valid or already used.');
386 }
387 })
388 },
390 '/info': function (conn, req, res, params) {
391 requireAccessLevel(conn, req, res, 'anonymous', function() {
392 var query = new selector.Selector();
393 query.from('"liquid_feedback_version"');
394 query.addField('"liquid_feedback_version".*');
395 db.query(conn, req, res, query, function (result, conn) {
396 var liquid_feedback_version = result.rows[0];
397 var query = new selector.Selector();
398 query.from('"system_setting"');
399 query.addField('"member_ttl"');
400 db.query(conn, req, res, query, function (result, conn) {
401 var member_ttl = null;
402 if (result.rows[0]) {
403 member_ttl = result.rows[0].member_ttl;
404 };
405 respond('json', conn, req, res, 'ok', {
406 core_version: liquid_feedback_version.string,
407 api_version: api_version,
408 current_access_level: req.current_member_id ? 'member' : req.current_access_level,
409 current_member_id: req.current_member_id,
410 member_ttl: member_ttl,
411 settings: config.settings
412 });
413 });
414 });
415 });
416 },
418 '/member_count': function (conn, req, res, params) {
419 requireAccessLevel(conn, req, res, 'anonymous', function() {
420 var query = new selector.Selector();
421 query.from('"member_count"');
422 query.addField('"member_count".*');
423 db.query(conn, req, res, query, function (result, conn) {
424 var member_count = result.rows[0];
425 respond('json', conn, req, res, 'ok', {
426 total_count: member_count.total_count,
427 calculated: member_count.calculated
428 });
429 });
430 });
431 },
433 '/contingent': function (conn, req, res, params) {
434 requireAccessLevel(conn, req, res, 'anonymous', function() {
435 var query = new selector.Selector();
436 query.from('"contingent"');
437 query.addField('"contingent".*');
438 db.query(conn, req, res, query, function (result, conn) {
439 respond('json', conn, req, res, 'ok', { result: result.rows });
440 });
441 });
442 },
444 '/contingent_left': function (conn, req, res, params) {
445 requireAccessLevel(conn, req, res, 'member', function() {
446 var query = new selector.Selector();
447 query.from('"member_contingent_left"');
448 query.addField('"member_contingent_left".text_entries_left');
449 query.addField('"member_contingent_left".initiatives_left');
450 query.addWhere(['member_id = ?', req.current_member_id]);
451 db.query(conn, req, res, query, function (result, conn) {
452 respond('json', conn, req, res, 'ok', { result: result.rows[0] });
453 });
454 });
455 },
457 '/member': function (conn, req, res, params) {
458 requireAccessLevel(conn, req, res, 'pseudonym', function() {
459 var query = new selector.Selector();
460 query.from('"member"');
461 if (req.current_access_level == 'pseudonym' && !req.current_member_id ) {
462 fields.addObjectFields(query, 'member', 'member_pseudonym');
463 } else {
464 fields.addObjectFields(query, 'member');
465 }
466 general_params.addMemberOptions(req, query, params);
467 query.addOrderBy('"member"."id"');
468 general_params.addLimitAndOffset(query, params);
469 db.query(conn, req, res, query, function (result, conn) {
470 respond('json', conn, req, res, 'ok', { result: result.rows });
471 });
472 });
473 },
475 '/member_history': function (conn, req, res, params) {
476 requireAccessLevel(conn, req, res, 'full', function() {
477 var query = new selector.Selector();
478 query.from('"member_history" JOIN "member" ON "member"."id" = "member_history"."member_id"');
479 query.addField('"member_history".*');
480 general_params.addMemberOptions(req, query, params);
481 query.addOrderBy('member_history.id');
482 general_params.addLimitAndOffset(query, params);
483 db.query(conn, req, res, query, function (member_history_result, conn) {
484 var result = { result: member_history_result.rows }
485 includes = [];
486 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
487 addRelatedData(conn, req, res, result, includes);
488 });
489 });
490 },
492 '/member_image': function (conn, req, res, params) {
493 requireAccessLevel(conn, req, res, 'full', function() {
494 var query = new selector.Selector();
495 query.from('"member_image" JOIN "member" ON "member"."id" = "member_image"."member_id"');
496 query.addField('"member_image".*');
497 query.addWhere('member_image.scaled');
498 general_params.addMemberOptions(req, query, params);
499 query.addOrderBy = ['member_image.member_id, member_image.image_type'];
500 db.query(conn, req, res, query, function (result, conn) {
501 respond('json', conn, req, res, 'ok', { result: result.rows });
502 });
503 });
504 },
506 '/contact': function (conn, req, res, params) {
507 requireAccessLevel(conn, req, res, 'pseudonym', function() {
508 var query = new selector.Selector();
509 query.from('contact JOIN member ON member.id = contact.member_id');
510 query.addField('"contact".*');
511 if (req.current_member_id) {
512 // public or own for members
513 query.addWhere(['"contact"."public" OR "contact"."member_id" = ?', req.current_member_id]);
514 } else {
515 // public for everybody
516 query.addWhere('"contact"."public"');
517 }
518 general_params.addMemberOptions(req, query, params);
519 query.addOrderBy('"contact"."id"');
520 general_params.addLimitAndOffset(query, params);
521 db.query(conn, req, res, query, function (result, conn) {
522 respond('json', conn, req, res, 'ok', { result: result.rows });
523 });
524 });
525 },
527 '/privilege': function (conn, req, res, params) {
528 requireAccessLevel(conn, req, res, 'pseudonym', function() {
529 var query = new selector.Selector();
530 query.from('privilege JOIN member ON member.id = privilege.member_id JOIN unit ON unit.id = privilege.unit_id');
531 query.addField('privilege.*');
532 general_params.addUnitOptions(req, query, params);
533 general_params.addMemberOptions(req, query, params);
534 query.addOrderBy('privilege.unit_id, privilege.member_id');
535 general_params.addLimitAndOffset(query, params);
536 db.query(conn, req, res, query, function (privilege_result, conn) {
537 var result = { result: privilege_result.rows }
538 includes = [];
539 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
540 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
541 addRelatedData(conn, req, res, result, includes);
542 });
543 });
544 },
546 '/policy': function (conn, req, res, params) {
547 requireAccessLevel(conn, req, res, 'anonymous', function() {
548 var query = new selector.Selector();
549 query.from('"policy"');
550 query.addField('"policy".*');
551 general_params.addPolicyOptions(req, query, params);
552 query.addOrderBy('"policy"."index"');
553 general_params.addLimitAndOffset(query, params);
554 db.query(conn, req, res, query, function (result, conn) {
555 respond('json', conn, req, res, 'ok', { result: result.rows });
556 });
557 });
558 },
560 '/unit': function (conn, req, res, params) {
561 requireAccessLevel(conn, req, res, 'anonymous', function() {
562 var query = new selector.Selector();
563 query.from('"unit"');
564 fields.addObjectFields(query, 'unit');
565 general_params.addUnitOptions(req, query, params);
566 query.addOrderBy('unit.id');
567 general_params.addLimitAndOffset(query, params);
568 db.query(conn, req, res, query, function (result, conn) {
569 respond('json', conn, req, res, 'ok', { result: result.rows });
570 });
571 });
572 },
574 '/area': function (conn, req, res, params) {
575 requireAccessLevel(conn, req, res, 'anonymous', function() {
576 var query = new selector.Selector();
577 query.from('area JOIN unit ON area.unit_id = unit.id');
578 fields.addObjectFields(query, 'area');
579 general_params.addAreaOptions(req, query, params);
580 query.addOrderBy('area.id');
581 general_params.addLimitAndOffset(query, params);
582 db.query(conn, req, res, query, function (area_result, conn) {
583 var result = { result: area_result.rows }
584 includes = [];
585 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
586 addRelatedData(conn, req, res, result, includes);
587 });
588 });
589 },
591 '/allowed_policy': function (conn, req, res, params) {
592 requireAccessLevel(conn, req, res, 'anonymous', function() {
593 var query = new selector.Selector();
594 query.from('allowed_policy');
595 query.join('area', null, 'area.id = allowed_policy.area_id');
596 query.join('unit', null, 'unit.id = area.unit_id');
597 query.addField('allowed_policy.*');
598 general_params.addAreaOptions(req, query, params);
599 query.addOrderBy('allowed_policy.area_id, allowed_policy.policy_id');
600 general_params.addLimitAndOffset(query, params);
601 db.query(conn, req, res, query, function (allowed_policy_result, conn) {
602 var result = { result: allowed_policy_result.rows }
603 includes = [];
604 if (params.include_policies) includes.push({ class: 'policy', objects: 'result'});
605 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
606 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
607 addRelatedData(conn, req, res, result, includes);
608 });
609 }); },
611 '/membership': function (conn, req, res, params) {
612 requireAccessLevel(conn, req, res, 'pseudonym', function() {
613 var query = new selector.Selector();
614 query.from('membership JOIN member ON membership.member_id = member.id JOIN area ON area.id = membership.area_id JOIN unit ON unit.id = area.unit_id');
615 query.addField('membership.*');
616 general_params.addAreaOptions(req, query, params);
617 general_params.addMemberOptions(req, query, params);
618 query.addOrderBy('membership.area_id, membership.member_id');
619 general_params.addLimitAndOffset(query, params);
620 db.query(conn, req, res, query, function (membership_result, conn) {
621 var result = { result: membership_result.rows }
622 includes = [];
623 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
624 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
625 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
626 addRelatedData(conn, req, res, result, includes);
627 });
628 });
629 },
631 '/issue': function (conn, req, res, params) {
632 requireAccessLevel(conn, req, res, 'anonymous', function() {
633 var query = new selector.Selector()
634 query.from('issue JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
635 fields.addObjectFields(query, 'issue');
636 general_params.addIssueOptions(req, query, params);
637 query.addOrderBy('issue.id');
638 general_params.addLimitAndOffset(query, params);
639 db.query(conn, req, res, query, function (issue_result, conn) {
640 var result = { result: issue_result.rows }
641 includes = [];
642 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
643 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
644 if (params.include_policies) includes.push({ class: 'policy', objects: 'result' });
645 addRelatedData(conn, req, res, result, includes);
646 });
647 });
648 },
650 '/population': function (conn, req, res, params) {
651 requireAccessLevel(conn, req, res, 'pseudonym', function() {
652 var query = new selector.Selector();
653 if (params.delegating == '1') {
654 query.from('delegating_population_snapshot', 'population');
655 if (params.delegate_member_id) {
656 query.addWhere(['population.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
657 }
658 if (params.direct_delegate_member_id) {
659 query.addWhere(['population.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
660 }
661 } else {
662 query.from('direct_population_snapshot', 'population');
663 }
664 switch (params.snapshot) {
665 case 'latest':
666 query.addWhere('population.event = issue.latest_snapshot_event');
667 break;
669 case 'end_of_admission':
670 case 'half_freeze':
671 case 'full_freeze':
672 query.addWhere(['population.event = ?', params.snapshot]);
673 break;
675 default:
676 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
677 return;
679 };
680 query.addField('population.*');
681 query.join('member', null, 'member.id = population.member_id');
682 query.join('issue', null, 'population.issue_id = issue.id');
683 query.join('policy', null, 'policy.id = issue.policy_id');
684 query.join('area', null, 'area.id = issue.area_id');
685 query.join('unit', null, 'area.unit_id = unit.id');
686 general_params.addMemberOptions(req, query, params);
687 general_params.addIssueOptions(req, query, params);
688 query.addOrderBy('population.issue_id, population.member_id');
689 general_params.addLimitAndOffset(query, params);
690 db.query(conn, req, res, query, function (population_result, conn) {
691 console.log(population_result);
692 var result = { result: population_result.rows }
693 includes = [];
694 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
695 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
696 if (params.include_areas) includes.push({ class: 'area', objects: 'areas'});
697 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
698 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
699 addRelatedData(conn, req, res, result, includes);
700 });
701 });
702 },
704 '/interest': function (conn, req, res, params) {
705 requireAccessLevel(conn, req, res, 'pseudonym', function() {
706 var query = new selector.Selector();
707 if (params.snapshot) {
708 if (params.delegating == '1') {
709 query.from('delegating_interest_snapshot', 'interest');
710 if (params.delegate_member_id) {
711 query.addWhere(['interest.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
712 }
713 if (params.direct_delegate_member_id) {
714 query.addWhere(['interest.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
715 }
716 } else {
717 query.from('direct_interest_snapshot', 'interest');
718 }
719 switch (params.snapshot) {
720 case 'latest':
721 query.addWhere('interest.event = issue.latest_snapshot_event');
722 break;
724 case 'end_of_admission':
725 case 'half_freeze':
726 case 'full_freeze':
727 query.addWhere(['interest.event = ?', params.snapshot]);
728 break;
730 default:
731 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
732 return;
734 };
735 } else {
736 if (! req.current_member_id) {
737 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
738 return;
739 };
740 query.from('interest');
741 query.addWhere(['interest.member_id = ?', req.current_member_id]);
742 }
743 query.addField('interest.*');
744 query.join('member', null, 'member.id = interest.member_id');
745 query.join('issue', null, 'interest.issue_id = issue.id');
746 query.join('policy', null, 'policy.id = issue.policy_id');
747 query.join('area', null, 'area.id = issue.area_id');
748 query.join('unit', null, 'area.unit_id = unit.id');
749 general_params.addMemberOptions(req, query, params);
750 general_params.addIssueOptions(req, query, params);
751 query.addOrderBy('interest.issue_id, interest.member_id');
752 general_params.addLimitAndOffset(query, params);
753 db.query(conn, req, res, query, function (interest_result, conn) {
754 var result = { result: interest_result.rows }
755 includes = [];
756 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
757 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
758 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
759 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
760 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
761 addRelatedData(conn, req, res, result, includes);
762 });
763 });
764 },
766 '/issue_comment': function (conn, req, res, params) {
767 requireAccessLevel(conn, req, res, 'pseudonym', function() {
768 var query = new selector.Selector();
769 query.from('issue_comment JOIN member ON member.id = issue_comment.member_id JOIN issue on issue_comment.issue_id = issue.id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
770 query.addField('issue_comment.*');
771 general_params.addMemberOptions(req, query, params);
772 general_params.addIssueOptions(req, query, params);
773 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
774 general_params.addLimitAndOffset(query, params);
775 db.query(conn, req, res, query, function (issue_comment_result, conn) {
776 var result = { result: issue_comment_result.rows }
777 includes = [];
778 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
779 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
780 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
781 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
782 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
783 addRelatedData(conn, req, res, result, includes);
784 });
785 });
786 },
788 '/initiative': function (conn, req, res, params) {
789 requireAccessLevel(conn, req, res, 'anonymous', function() {
790 var query = new selector.Selector();
791 query.from('initiative JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
792 fields.addObjectFields(query, 'initiative');
793 query.addOrderBy('initiative.id');
794 general_params.addInitiativeOptions(req, query, params);
795 general_params.addLimitAndOffset(query, params);
796 db.query(conn, req, res, query, function (initiative_result, conn) {
797 var result = { result: initiative_result.rows }
798 includes = [];
799 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
800 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
801 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
802 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
803 addRelatedData(conn, req, res, result, includes);
804 });
805 });
806 },
808 '/initiator': function (conn, req, res, params) {
809 requireAccessLevel(conn, req, res, 'pseudonym', function() {
810 var fields = ['initiator.initiative_id', 'initiator.member_id'];
811 var query = new selector.Selector();
812 query.from('initiator JOIN member ON member.id = initiator.member_id JOIN initiative ON initiative.id = initiator.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
813 query.addWhere('initiator.accepted');
814 fields.forEach( function(field) {
815 query.addField(field, null, ['grouped']);
816 });
817 general_params.addMemberOptions(req, query, params);
818 general_params.addInitiativeOptions(req, query, params);
819 query.addOrderBy('initiator.initiative_id, initiator.member_id');
820 general_params.addLimitAndOffset(query, params);
821 db.query(conn, req, res, query, function (initiator, conn) {
822 var result = { result: initiator.rows }
823 includes = [];
824 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
825 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
826 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
827 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
828 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
829 addRelatedData(conn, req, res, result, includes);
830 });
831 });
832 },
835 '/supporter': function (conn, req, res, params) {
836 requireAccessLevel(conn, req, res, 'pseudonym', function() {
837 var fields = ['supporter.issue_id', 'supporter.initiative_id', 'supporter.member_id', 'supporter.draft_id'];
838 var query = new selector.Selector();
839 query.from('supporter')
840 query.join('member', null, 'member.id = supporter.member_id JOIN initiative ON initiative.id = supporter.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
841 fields.forEach( function(field) {
842 query.addField(field, null, ['grouped']);
843 });
844 general_params.addMemberOptions(req, query, params);
845 general_params.addInitiativeOptions(req, query, params);
846 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
847 general_params.addLimitAndOffset(query, params);
848 db.query(conn, req, res, query, function (supporter, conn) {
849 var result = { result: supporter.rows }
850 includes = [];
851 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
852 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
853 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
854 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
855 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
856 addRelatedData(conn, req, res, result, includes);
857 });
858 });
859 },
861 '/battle': function (conn, req, res, params) {
862 requireAccessLevel(conn, req, res, 'anonymous', function() {
863 var query = new selector.Selector();
864 query.from('battle JOIN initiative ON initiative.id = battle.winning_initiative_id OR initiative.id = battle.losing_initiative_id JOIN issue ON issue.id = battle.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
865 query.addField('battle.*');
866 general_params.addInitiativeOptions(req, query, params);
867 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
868 general_params.addLimitAndOffset(query, params);
869 db.query(conn, req, res, query, function (result, conn) {
870 var result = { result: result.rows }
871 includes = [];
872 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
873 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
874 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
875 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
876 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
877 addRelatedData(conn, req, res, result, includes);
878 });
879 });
880 },
882 '/draft': function (conn, req, res, params) {
883 requireAccessLevel(conn, req, res, 'anonymous', function() {
884 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
885 var query = new selector.Selector();
886 query.from('draft JOIN initiative ON initiative.id = draft.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
887 fields.forEach( function(field) {
888 query.addField(field, null, ['grouped']);
889 });
890 if (req.current_access_level != 'anonymous' || req.current_member_id) {
891 query.addField('draft.author_id');
892 }
893 if (params.draft_id) {
894 query.addWhere('draft.id = ?', params.draft_id);
895 }
896 if (params.current_draft) {
897 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
898 }
899 general_params.addInitiativeOptions(req, query, params);
900 query.addOrderBy('draft.initiative_id, draft.id');
901 general_params.addLimitAndOffset(query, params);
902 db.query(conn, req, res, query, function (result, conn) {
903 var result = { result: result.rows }
904 includes = [];
905 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
906 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
907 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
908 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
909 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
910 addRelatedData(conn, req, res, result, includes);
911 });
912 });
913 },
915 '/suggestion': function (conn, req, res, params) {
916 requireAccessLevel(conn, req, res, 'anonymous', function() {
917 var query = new selector.Selector();
918 query.from('suggestion JOIN initiative ON initiative.id = suggestion.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
919 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
920 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
921 } else {
922 fields.addObjectFields(query, 'suggestion');
923 }
924 general_params.addSuggestionOptions(req, query, params);
925 query.addOrderBy('suggestion.initiative_id, suggestion.id');
926 general_params.addLimitAndOffset(query, params);
927 db.query(conn, req, res, query, function (result, conn) {
928 var result = { result: result.rows }
929 includes = [];
930 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
931 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
932 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
933 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
934 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
935 addRelatedData(conn, req, res, result, includes);
936 });
937 });
938 },
940 '/opinion': function (conn, req, res, params) {
941 requireAccessLevel(conn, req, res, 'pseudonym', function() {
942 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
943 var query = new selector.Selector();
944 query.from('opinion JOIN member ON member.id = opinion.member_id JOIN suggestion ON suggestion.id = opinion.suggestion_id JOIN initiative ON initiative.id = suggestion.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
945 fields.forEach( function(field) {
946 query.addField(field, null, ['grouped']);
947 });
948 general_params.addMemberOptions(req, query, params);
949 general_params.addSuggestionOptions(req, query, params);
950 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
951 general_params.addLimitAndOffset(query, params);
952 db.query(conn, req, res, query, function (result, conn) {
953 var result = { result: result.rows }
954 includes = [];
955 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
956 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
957 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
958 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
959 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
960 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
961 addRelatedData(conn, req, res, result, includes);
962 });
963 });
964 },
966 '/delegation': function (conn, req, res, params) {
967 requireAccessLevel(conn, req, res, 'pseudonym', function() {
968 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
969 var query = new selector.Selector();
970 query.from('delegation LEFT JOIN issue on delegation.issue_id = issue.id LEFT JOIN policy ON policy.id = issue.policy_id LEFT JOIN area ON area.id = issue.area_id OR area.id = delegation.area_id LEFT JOIN unit ON area.unit_id = unit.id OR unit.id = delegation.unit_id');
971 fields.forEach( function(field) {
972 query.addField(field, null, ['grouped']);
973 });
974 if (params.direction) {
975 switch (params.direction) {
976 case 'in':
977 query.join('member', null, 'member.id = delegation.trustee_id');
978 break;
979 case 'out':
980 query.join('member', null, 'member.id = delegation.truster_id');
981 break;
982 default:
983 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
984 }
985 } else {
986 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
987 }
988 general_params.addMemberOptions(req, query, params);
989 general_params.addIssueOptions(req, query, params);
990 if (params.scope) {
991 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
992 };
993 query.addOrderBy = ['delegation.id'];
994 general_params.addLimitAndOffset(query, params);
995 db.query(conn, req, res, query, function (result, conn) {
996 respond('json', conn, req, res, 'ok', { result: result.rows });
997 });
998 });
999 },
1001 '/vote': function (conn, req, res, params) {
1002 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1003 var query = new selector.Selector();
1004 query.from('vote JOIN member ON member.id = vote.member_id JOIN initiative ON initiative.id = vote.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
1005 query.addField('vote.*');
1006 query.addWhere('issue.closed_at NOTNULL');
1007 general_params.addMemberOptions(req, query, params);
1008 general_params.addInitiativeOptions(req, query, params);
1009 general_params.addLimitAndOffset(query, params);
1010 db.query(conn, req, res, query, function (result, conn) {
1011 respond('json', conn, req, res, 'ok', { result: result.rows });
1012 });
1013 });
1014 },
1016 '/event': function (conn, req, res, params) {
1017 requireAccessLevel(conn, req, res, 'anonymous', function() {
1018 var fields = ['event.id', 'event.occurrence', 'event.event', 'event.member_id', 'event.issue_id', 'event.state', 'event.initiative_id', 'event.draft_id', 'event.suggestion_id'];
1019 var query = new selector.Selector();
1020 query.from('event LEFT JOIN member ON member.id = event.member_id LEFT JOIN initiative ON initiative.id = event.initiative_id LEFT JOIN issue ON issue.id = event.issue_id LEFT JOIN policy ON policy.id = issue.policy_id LEFT JOIN area ON area.id = issue.area_id LEFT JOIN unit ON area.unit_id = unit.id');
1021 fields.forEach( function(field) {
1022 query.addField(field, null, ['grouped']);
1023 });
1024 general_params.addMemberOptions(req, query, params);
1025 general_params.addInitiativeOptions(req, query, params);
1026 query.addOrderBy('event.id');
1027 general_params.addLimitAndOffset(query, params);
1028 db.query(conn, req, res, query, function (events, conn) {
1029 var result = { result: events.rows }
1030 includes = [];
1031 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
1032 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
1033 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1034 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1035 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1036 addRelatedData(conn, req, res, result, includes);
1037 });
1038 });
1039 },
1041 // TODO add interfaces for data structure:
1042 // ignored_member requireAccessLevel(conn, req, res, 'member');
1043 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1044 // setting requireAccessLevel(conn, req, res, 'member');
1046 };
1048 // ==========================================================================
1049 // POST methods
1050 // ==========================================================================
1054 exports.post = {
1056 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1057 respond('json', conn, req, res, 'ok', { result: params });
1058 }); },
1060 '/register_test': function (conn, req, res, params) {
1061 var understood = params.understood;
1062 var member_login = randomString(16);
1063 var member_name = params.name;
1064 var member_password = randomString(16);
1065 var member_notify_email = params.email;
1066 var member_notify_email_secret = randomString(24);
1067 var api_key_member = randomString(24);
1068 var api_key_full = randomString(24);
1069 var api_key_pseudonym = randomString(24);
1070 var api_key_anonymous = randomString(24);
1072 if (understood != 'understood') {
1073 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1074 return;
1077 // add member
1078 var query = new selector.SQLInsert('member');
1079 query.addValues({
1080 login: member_login,
1081 password: member_password, // TODO hashing of password
1082 notify_email_unconfirmed: member_notify_email,
1083 notify_email_secret: member_notify_email_secret,
1084 name: member_name
1085 });
1086 query.addReturning('id');
1087 db.query(conn, req, res, query, function (result, conn) {
1088 var member_id = result.rows[0].id;
1090 // add privilege for root unit
1091 var query = new selector.SQLInsert('privilege');
1092 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1093 db.query(conn, req, res, query, function (result, conn) {
1095 var location = params.location;
1096 var unit_id;
1097 switch(location) {
1098 case 'earth':
1099 unit_id = 3;
1100 break;
1101 case 'moon':
1102 unit_id = 4;
1103 break;
1104 case 'mars':
1105 unit_id = 5;
1106 break;
1109 // add privilege for selected planet
1110 var query = new selector.SQLInsert('privilege');
1111 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1112 db.query(conn, req, res, query, function (result, conn) {
1114 // add application key
1115 var query = new selector.SQLInsert('member_application');
1116 query.addValues({
1117 member_id: member_id,
1118 name: 'member',
1119 comment: 'access_level member',
1120 access_level: 'member',
1121 key: api_key_member
1122 });
1123 query.addReturning('id');
1125 db.query(conn, req, res, query, function (result, conn) {
1127 nodemailer.sendmail = '/usr/sbin/sendmail';
1129 // send email to user
1130 nodemailer.send_mail({
1131 sender: config.mail.from,
1132 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1133 to: member_notify_email,
1134 body: "\
1135 Hello " + member_name + ",\n\
1136 \n\
1137 thank you for registering at the public alpha test of the LiquidFeedback\n\
1138 application programming interface. To complete the registration process,\n\
1139 you need to confirm your email address by opening the following URL:\n\
1140 \n\
1141 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1142 \n\
1143 \n\
1144 After you've confirmed your email address, your account will be automatically\n\
1145 activated.\n\
1146 \n\
1147 Your account name is: " + member_name + "\n\
1148 \n\
1149 \n\
1150 You will need the following login and password to register and unregister\n\
1151 applications for your account later. This function is currently not\n\
1152 implemented, but please keep the credentials for future use.\n\
1153 \n\
1154 Account ID: " + member_id + "\n\
1155 Login: " + member_login + "\n\
1156 Password: " + member_password + "\n\
1157 \n\
1158 \n\
1159 To make you able to actually access the API interface, we added the following\n\
1160 application key with full member access privileges to your account:\n\
1161 \n\
1162 API Key: " + api_key_member + "\n\
1163 \n\
1164 \n\
1165 The base address of the public test is: " + config.public_url_path + "\n\
1166 \n\
1167 The programming interface is described in the LiquidFeedback API\n\
1168 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1169 \n\
1170 The current implementation status of lfapi is published at the LiquidFeedback\n\
1171 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1172 \n\
1173 If you have any questions or suggestions, please use our public mailing list\n\
1174 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1175 \n\
1176 For issues regarding your test account, contact us via email at\n\
1177 lqfb-maintainers@public-software-group.org\n\
1178 \n\
1179 \n\
1180 Sincerely,\n\
1181 \n\
1182 Your LiquidFeedback maintainers",
1183 },
1184 function(err, result){
1185 if(err){ console.log(err); }
1186 });
1188 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1189 });
1190 });
1191 });
1192 });
1193 },
1195 /*
1196 '/register': function (conn, req, res, params) {
1197 var invite_key = params.invite_key;
1198 var login = params.login;
1199 var password = params.password;
1200 var name = params.name;
1201 var notify_email = params.notify_email;
1202 if (!invite_key) {
1203 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1204 return;
1205 };
1206 if (!login) {
1207 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1208 return;
1209 };
1210 if (!password) {
1211 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1212 return;
1213 };
1214 if (!name) {
1215 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1216 return;
1217 };
1218 if (!notify_email) {
1219 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1220 return;
1221 };
1222 // check if akey is valid and get member_id for akey
1223 db.query(conn, req, res, { select: ['member.id'], from: ['member'], where: ['NOT member.activation AND member.invite_key = ' + db.pgEncode(invite_key)] }, function (result, conn) {
1224 if (result.rows.length != 1) {
1225 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1226 return;
1227 };
1228 var member_id = result.rows[0].id;
1229 // check if name is available
1230 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1231 if (result.rows.length > 0) {
1232 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1233 return;
1234 };
1235 // check if login is available
1236 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1237 if (result.rows.length > 0) {
1238 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1239 return;
1240 };
1241 var query = { update: 'member', set: { activation: 'now', active: true, } };
1243 });
1244 });
1245 });
1246 },
1247 */
1249 '/session': function (conn, req, res, params) {
1250 var key = params.key;
1251 if (!key) {
1252 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1253 return;
1254 };
1255 var query = new selector.Selector();
1256 query.from('member');
1257 query.join('member_application', null, 'member_application.member_id = member.id');
1258 query.addField('member.id');
1259 query.addWhere(['member.activated NOTNULL AND member_application.key = ?', key]);
1260 if (params.interactive) {
1261 query.forUpdateOf('member');
1263 db.query(conn, req, res, query, function (result, conn) {
1264 if (result.rows.length != 1) {
1265 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1266 return;
1267 };
1268 var member_id = result.rows[0].id;
1269 var session_key = randomString(16);
1270 req.sessions[session_key] = member_id;
1271 var query;
1272 if (params.interactive) {
1273 query = new selector.SQLUpdate('member');
1274 query.addWhere(['member.id = ?', member_id]);
1275 query.addValues({ last_activity: 'now' });
1277 db.query(conn, req, res, query, function (result, conn) {
1278 respond('json', conn, req, res, 'ok', { session_key: session_key });
1279 });
1280 });
1281 },
1283 '/member': function (conn, req, res, params) {
1284 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1285 requireAccessLevel(conn, req, res, 'member', function() {
1286 var query = new selector.SQLUpdate('member');
1287 query.addWhere(['member.id = ?', req.current_member_id]);
1288 fields.forEach( function(field) {
1289 if (typeof(params[field]) != 'undefined') {
1290 query.addValues({ field: params[field] });
1291 } else {
1292 query.addValues({ field: null });
1294 });
1295 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1296 });
1297 },
1299 '/membership': function (conn, req, res, params) {
1300 requireAccessLevel(conn, req, res, 'member', function() {
1302 // check if area_id is set
1303 var area_id = parseInt(params.area_id);
1304 if (!area_id) {
1305 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1306 return;
1309 // delete membership
1310 if (params.delete) {
1311 var query;
1312 query = new selector.SQLDelete('membership');
1313 query.addWhere(['area_id = ?', area_id]);
1314 query.addWhere(['member_id = ?', req.current_member_id]);
1315 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1317 // add membership
1318 } else {
1320 // lock member for upsert
1321 lockMemberById(conn, req, res, req.current_member_id, function() {
1323 // check and lock privilege
1324 requireAreaPrivilege(conn, req, res, area_id, function() {
1326 // upsert membership
1327 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1328 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1329 db.query(conn, req, res, query, function(result) {
1330 respond('json', conn, req, res, 'ok');
1331 });
1332 });
1333 });
1335 });
1336 },
1338 '/interest': function (conn, req, res, params) {
1339 requireAccessLevel(conn, req, res, 'member', function() {
1340 var query;
1342 // check if issue_id is set
1343 var issue_id = parseInt(params.issue_id);
1344 if (!issue_id) {
1345 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1346 return;
1349 // lock member for upsert
1350 lockMemberById(conn, req, res, req.current_member_id, function() {
1352 // delete interest
1353 if (params.delete) {
1355 // check issue state
1356 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1358 // delete interest
1359 query = new selector.SQLDelete('interest');
1360 query.addWhere(['issue_id = ?', issue_id]);
1361 query.addWhere(['member_id = ?', req.current_member_id]);
1362 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1363 });
1365 // add interest
1366 } else {
1368 // check and lock privilege
1369 requireIssuePrivilege(conn, req, res, issue_id, function() {
1371 // check issue state
1372 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1374 // upsert interest
1375 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1376 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1377 db.query(conn, req, res, query, function(result) {
1378 respond('json', conn, req, res, 'ok');
1379 });
1380 });
1381 });
1382 };
1383 });
1384 });
1385 },
1387 '/issue_comment': function (conn, req, res, params) {
1388 requireAccessLevel(conn, req, res, 'member', function() {
1390 var issue_id = parseInt(params.issue_id);
1391 var formatting_engine = params.formatting_engine
1392 var content = params.content;
1394 if (!issue_id) {
1395 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1396 return;
1399 // delete issue comment
1400 if (params.delete) {
1401 var query;
1402 query = new selector.SQLDelete('issue_comment');
1403 query.addWhere(['issue_id = ?', params.issue_id]);
1404 query.addWhere(['member_id = ?', req.current_member_id]);
1405 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1407 // upsert issue comment
1408 } else {
1410 // check if formatting engine is supplied and valid
1411 if (!formatting_engine) {
1412 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1413 return;
1414 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1415 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1416 return;
1417 };
1419 // check if content is supplied
1420 if (!content) {
1421 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1422 return;
1425 // lock member for upsert
1426 lockMemberById(conn, req, res, req.current_member_id, function() {
1428 // check and lock privilege
1429 requireIssuePrivilege(conn, req, res, issue_id, function() {
1431 // upsert issue comment
1432 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1433 query.addValues({
1434 issue_id: issue_id,
1435 member_id: req.current_member_id,
1436 changed: 'now',
1437 formatting_engine: formatting_engine,
1438 content: content
1439 });
1441 db.query(conn, req, res, query, function(result) {
1442 respond('json', conn, req, res, 'ok');
1443 });
1445 });
1446 });
1450 });
1451 },
1453 '/voting_comment': function (conn, req, res, params) {
1454 requireAccessLevel(conn, req, res, 'member', function() {
1456 var issue_id = parseInt(params.issue_id);
1457 var formatting_engine = params.formatting_engine
1458 var content = params.content;
1460 if (!issue_id) {
1461 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1462 return;
1466 // delete voting comment
1467 if (params.delete) {
1468 var query;
1469 query = new selector.SQLDelete('voting_comment');
1470 query.addWhere(['issue_id = ?', params.issue_id]);
1471 query.addWhere(['member_id = ?', req.current_member_id]);
1472 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1474 // upsert voting comment
1475 } else {
1477 // check if formatting engine is supplied and valid
1478 if (!formatting_engine) {
1479 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1480 return;
1481 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1482 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1483 return;
1484 };
1486 // check if content is supplied
1487 if (!content) {
1488 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1489 return;
1492 // lock member for upsert
1493 lockMemberById(conn, req, res, req.current_member_id, function() {
1495 // check and lock privilege
1496 requireIssuePrivilege(conn, req, res, issue_id, function() {
1498 // check issue state
1499 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1501 // upsert voting comment
1502 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1503 query.addValues({
1504 issue_id: issue_id,
1505 member_id: req.current_member_id,
1506 changed: 'now',
1507 formatting_engine: formatting_engine,
1508 content: content
1509 });
1511 db.query(conn, req, res, query, function(result) {
1512 respond('json', conn, req, res, 'ok');
1513 });
1515 });
1516 });
1517 })
1518 };
1519 });
1520 },
1522 '/supporter': function (conn, req, res, params) {
1523 requireAccessLevel(conn, req, res, 'member', function() {
1524 var initiative_id = parseInt(params.initiative_id);
1525 var draft_id = parseInt(params.draft_id);
1527 // check if needed arguments are supplied
1528 if (!initiative_id) {
1529 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1530 return;
1533 if (!draft_id) {
1534 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1535 return;
1538 // lock member for upsert
1539 lockMemberById(conn, req, res, req.current_member_id, function() {
1541 // delete supporter
1542 if (params.delete) {
1544 // check issue state
1545 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1547 // delete supporter
1548 var query = new selector.SQLDelete('supporter');
1549 query.addWhere(['initiative_id = ?', initiative_id]);
1550 query.addWhere(['member_id = ?', req.current_member_id]);
1551 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1553 });
1555 // upsert supporter
1556 } else {
1558 // check and lock privilege
1559 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1561 // check issue state
1562 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1564 // check if given draft is the current one
1565 var query = new selector.Selector('current_draft');
1566 query.addField('NULL');
1567 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1568 query.addWhere(['current_draft.id = ?', draft_id]);
1570 db.query(conn, req, res, query, function(result) {
1571 if (result.rows.length != 1) {
1572 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1573 return;
1576 // upsert supporter
1577 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1578 query.addValues({
1579 initiative_id: initiative_id,
1580 member_id: req.current_member_id,
1581 draft_id: draft_id
1582 });
1584 db.query(conn, req, res, query, function(result) {
1585 respond('json', conn, req, res, 'ok');
1586 });
1588 });
1589 });
1590 });
1591 };
1592 });
1593 });
1594 },
1596 '/draft': function (conn, req, res, params) {
1597 requireAccessLevel(conn, req, res, 'member', function() {
1598 var area_id = parseInt(params.area_id);
1599 var policy_id = parseInt(params.policy_id);
1600 var issue_id = parseInt(params.issue_id);
1601 var initiative_id = parseInt(params.initiative_id);
1602 var initiative_name = params.initiative_name;
1603 var initiative_discussion_url = params.initiative_discussion_url;
1604 var formatting_engine = params.formatting_engine;
1605 var content = params.content;
1607 if (!initiative_discussion_url) initiative_discussion_url = null;
1609 // check parameters
1610 if (!formatting_engine) {
1611 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1612 return;
1613 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1614 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1615 return;
1616 };
1618 if (!content) {
1619 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1620 return;
1621 };
1623 lockMemberById(conn, req, res, req.current_member_id, function() {
1625 // new draft in new initiative in new issue
1626 if (area_id && !issue_id && !initiative_id) {
1628 // check parameters for new issue
1629 if (!policy_id) {
1630 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1631 return;
1634 if (!initiative_name) {
1635 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1636 return;
1639 requireAreaPrivilege(conn, req, res, area_id, function() {
1641 // check if policy is allowed in this area and if area and policy are active
1642 var query = new selector.Selector();
1643 query.from('allowed_policy');
1644 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1645 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1646 query.addField('NULL');
1647 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1648 db.query(conn, req, res, query, function (result, conn) {
1649 if (result.rows.length != 1) {
1650 respond('json', conn, req, res, 'unprocessable', null, 'Area and/or policy doesn\'t exist, area and/or policy is not active or policy is not allowed in this area.');
1651 return;
1652 };
1654 // check contingent
1655 requireContingentLeft(conn, req, res, true, function() {
1657 // insert new issue
1658 var query = new selector.SQLInsert('issue');
1659 query.addValues({
1660 area_id: area_id,
1661 policy_id: policy_id
1662 });
1663 query.addReturning('id');
1664 db.query(conn, req, res, query, function(result) {
1665 var issue_id = result.rows[0].id;
1667 // insert new initiative
1668 var query = new selector.SQLInsert('initiative');
1669 query.addValues({
1670 issue_id: issue_id,
1671 name: initiative_name,
1672 discussion_url: initiative_discussion_url
1673 });
1674 query.addReturning('id');
1675 db.query(conn, req, res, query, function(result) {
1676 var initiative_id = result.rows[0].id;
1678 // insert initiator
1679 var query = new selector.SQLInsert('initiator');
1680 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1681 db.query(conn, req, res, query, function(result) {
1683 // insert new draft
1684 var query = new selector.SQLInsert('draft');
1685 query.addValues({
1686 initiative_id: initiative_id,
1687 author_id: req.current_member_id,
1688 formatting_engine: formatting_engine,
1689 content: content
1690 });
1691 query.addReturning('id');
1692 db.query(conn, req, res, query, function (result, conn) {
1693 var draft_id = result.rows[0].id;
1695 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1696 });
1697 });
1698 });
1699 });
1700 });
1701 });
1702 });
1704 // new draft in new initiative in existant issue
1705 } else if (issue_id && !area_id && !initiative_id) {
1707 if (!initiative_name) {
1708 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1709 return;
1712 // check privilege
1713 requireIssuePrivilege(conn, req, res, issue_id, function() {
1715 // check issue state
1716 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1718 // check contingent
1719 requireContingentLeft(conn, req, res, true, function() {
1721 // insert initiative
1722 var query = new selector.SQLInsert('initiative');
1723 query.addValues({
1724 issue_id: issue_id,
1725 name: initiative_name,
1726 discussion_url: initiative_discussion_url
1727 });
1728 query.addReturning('id');
1729 db.query(conn, req, res, query, function(result) {
1730 var initiative_id = result.rows[0].id;
1732 // insert initiator
1733 var query = new selector.SQLInsert('initiator');
1734 query.addValues({
1735 initiative_id: initiative_id,
1736 member_id: req.current_member_id,
1737 accepted: true
1738 });
1739 db.query(conn, req, res, query, function(result) {
1741 // insert draft
1742 var query = new selector.SQLInsert('draft');
1743 query.addValues({
1744 initiative_id: initiative_id,
1745 author_id: req.current_member_id,
1746 formatting_engine: formatting_engine,
1747 content: content
1748 });
1749 query.addReturning('id');
1750 db.query(conn, req, res, query, function (result, conn) {
1752 var draft_id = result.rows[0].id;
1753 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1755 });
1756 });
1757 });
1758 });
1759 });
1760 });
1762 // new draft in existant initiative
1763 } else if (initiative_id && !area_id && !issue_id ) {
1765 // check privilege
1766 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1768 // check issue state
1769 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1772 // get initiator
1773 var query = new selector.Selector();
1774 query.from('initiator');
1775 query.addField('accepted');
1776 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1777 db.query(conn, req, res, query, function (result, conn) {
1779 // if member is not initiator, deny creating new draft
1780 if (result.rows.length != 1) {
1781 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1782 return;
1784 var initiator = result.rows[0];
1785 if (!initiator.accepted) {
1786 respond('json', conn, req, res, 'forbidden', null, 'You have been invited as initiator, but haven\'t accepted invitation and you are not allowed to update this initiative.');
1787 return;
1788 };
1790 // check contingent
1791 requireContingentLeft(conn, req, res, false, function() {
1793 // insert new draft
1794 var query = new selector.SQLInsert('draft');
1795 query.addValues({
1796 initiative_id: initiative_id,
1797 author_id: req.current_member_id,
1798 formatting_engine: formatting_engine,
1799 content: content
1800 });
1801 query.addReturning('id');
1802 db.query(conn, req, res, query, function (result, conn) {
1804 var draft_id = result.rows[0].id;
1805 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1806 });
1807 });
1808 });
1809 });
1810 });
1812 // none of them (invalid request)
1813 } else {
1814 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1815 };
1817 });
1818 });
1819 },
1821 '/suggestion': function (conn, req, res, params) {
1822 requireAccessLevel(conn, req, res, 'member', function() {
1823 // TODO
1824 });
1825 },
1827 '/opinion': function (conn, req, res, params) {
1828 requireAccessLevel(conn, req, res, 'member', function() {
1829 // TODO
1830 });
1831 },
1833 '/delegation': function (conn, req, res, params) {
1834 requireAccessLevel(conn, req, res, 'member', function() {
1835 var unit_id = parseInt(params.unit_id);
1836 var area_id = parseInt(params.area_id);
1837 var issue_id = parseInt(params.issue_id);
1838 var trustee_id;
1840 if (params.trustee_id == '') {
1841 trustee_id = null;
1842 } else {
1843 trustee_id = parseInt(params.trustee_id);
1846 lockMemberById(conn, req, res, req.current_member_id, function() {
1848 if (params.delete) {
1849 var query = new selector.SQLDelete('delegation')
1850 if (unit_id && !area_id && !issue_id) {
1851 query.addWhere(['unit_id = ?', unit_id]);
1852 } else if (!unit_id && area_id && !issue_id) {
1853 query.addWhere(['area_id = ?', area_id]);
1854 } else if (!unit_id && !area_id && issue_id) {
1855 query.addWhere(['issue_id = ?', issue_id]);
1856 } else {
1857 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1858 return;
1860 query.addWhere(['truster_id = ?', req.current_member_id]);
1861 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1862 } else {
1863 var query = new selector.Upserter('delegation', ['truster_id']);
1864 query.addValues({
1865 truster_id: req.current_member_id,
1866 trustee_id: trustee_id
1867 });
1868 if (unit_id && !area_id && !issue_id) {
1870 // check privilege
1871 requireUnitPrivilege(conn, req, res, unit_id, function() {
1873 query.addKeys(['unit_id'])
1874 query.addValues({ unit_id: unit_id, scope: 'unit' });
1875 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1876 });
1878 } else if (!unit_id && area_id && !issue_id) {
1880 // check privilege
1881 requireAreaPrivilege(conn, req, res, area_id, function() {
1883 query.addKeys(['area_id'])
1884 query.addValues({ area_id: area_id, scope: 'area' });
1885 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1886 });
1888 } else if (!unit_id && !area_id && issue_id) {
1890 // check privilege
1891 requireIssuePrivilege(conn, req, res, issue_id, function() {
1893 // check issue state
1894 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1896 query.addKeys(['issue_id'])
1897 query.addValues({ issue_id: issue_id, scope: 'issue' });
1898 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1899 });
1900 });
1901 } else {
1902 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1903 return;
1907 });
1909 });
1910 },
1912 };

Impressum / About Us