lfapi

view lfapi/main.js @ 21:2fcdef9f0e9c

Added support for delegating population
author bsw
date Sun Nov 06 19:40:56 2011 +0100 (2011-11-06)
parents da041f00018a
children fef5d8aad4aa
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 switch (params.snapshot) {
708 case 'latest':
709 query.from('direct_interest_snapshot', 'interest');
710 query.addWhere('interest.event = issue.latest_snapshot_event');
711 break;
713 case 'end_of_admission':
714 case 'half_freeze':
715 case 'full_freeze':
716 query.from('direct_interest_snapshot', 'interest');
717 query.addWhere(['interest.event = ?', params.snapshot]);
718 break;
720 case undefined:
721 if (! req.current_member_id) {
722 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
723 return;
724 };
725 query.from('interest');
726 query.addWhere(['interest.member_id = ?', req.current_member_id]);
727 break;
729 default:
730 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
731 return;
733 };
734 query.addField('interest.*');
735 query.join('member', null, 'member.id = interest.member_id');
736 query.join('issue', null, 'interest.issue_id = issue.id');
737 query.join('policy', null, 'policy.id = issue.policy_id');
738 query.join('area', null, 'area.id = issue.area_id');
739 query.join('unit', null, 'area.unit_id = unit.id');
740 general_params.addMemberOptions(req, query, params);
741 general_params.addIssueOptions(req, query, params);
742 query.addOrderBy('interest.issue_id, interest.member_id');
743 general_params.addLimitAndOffset(query, params);
744 db.query(conn, req, res, query, function (interest_result, conn) {
745 var result = { result: interest_result.rows }
746 includes = [];
747 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
748 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
749 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
750 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
751 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
752 addRelatedData(conn, req, res, result, includes);
753 });
754 });
755 },
757 '/issue_comment': function (conn, req, res, params) {
758 requireAccessLevel(conn, req, res, 'pseudonym', function() {
759 var query = new selector.Selector();
760 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');
761 query.addField('issue_comment.*');
762 general_params.addMemberOptions(req, query, params);
763 general_params.addIssueOptions(req, query, params);
764 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
765 general_params.addLimitAndOffset(query, params);
766 db.query(conn, req, res, query, function (issue_comment_result, conn) {
767 var result = { result: issue_comment_result.rows }
768 includes = [];
769 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
770 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
771 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
772 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
773 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
774 addRelatedData(conn, req, res, result, includes);
775 });
776 });
777 },
779 '/initiative': function (conn, req, res, params) {
780 requireAccessLevel(conn, req, res, 'anonymous', function() {
781 var query = new selector.Selector();
782 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');
783 fields.addObjectFields(query, 'initiative');
784 query.addOrderBy('initiative.id');
785 general_params.addInitiativeOptions(req, query, params);
786 general_params.addLimitAndOffset(query, params);
787 db.query(conn, req, res, query, function (initiative_result, conn) {
788 var result = { result: initiative_result.rows }
789 includes = [];
790 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
791 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
792 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
793 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
794 addRelatedData(conn, req, res, result, includes);
795 });
796 });
797 },
799 '/initiator': function (conn, req, res, params) {
800 requireAccessLevel(conn, req, res, 'pseudonym', function() {
801 var fields = ['initiator.initiative_id', 'initiator.member_id'];
802 var query = new selector.Selector();
803 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');
804 query.addWhere('initiator.accepted');
805 fields.forEach( function(field) {
806 query.addField(field, null, ['grouped']);
807 });
808 general_params.addMemberOptions(req, query, params);
809 general_params.addInitiativeOptions(req, query, params);
810 query.addOrderBy('initiator.initiative_id, initiator.member_id');
811 general_params.addLimitAndOffset(query, params);
812 db.query(conn, req, res, query, function (initiator, conn) {
813 var result = { result: initiator.rows }
814 includes = [];
815 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
816 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
817 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
818 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
819 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
820 addRelatedData(conn, req, res, result, includes);
821 });
822 });
823 },
826 '/supporter': function (conn, req, res, params) {
827 requireAccessLevel(conn, req, res, 'pseudonym', function() {
828 var fields = ['supporter.issue_id', 'supporter.initiative_id', 'supporter.member_id', 'supporter.draft_id'];
829 var query = new selector.Selector();
830 query.from('supporter')
831 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');
832 fields.forEach( function(field) {
833 query.addField(field, null, ['grouped']);
834 });
835 general_params.addMemberOptions(req, query, params);
836 general_params.addInitiativeOptions(req, query, params);
837 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
838 general_params.addLimitAndOffset(query, params);
839 db.query(conn, req, res, query, function (supporter, conn) {
840 var result = { result: supporter.rows }
841 includes = [];
842 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
843 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
844 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
845 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
846 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
847 addRelatedData(conn, req, res, result, includes);
848 });
849 });
850 },
852 '/battle': function (conn, req, res, params) {
853 requireAccessLevel(conn, req, res, 'anonymous', function() {
854 var query = new selector.Selector();
855 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');
856 query.addField('battle.*');
857 general_params.addInitiativeOptions(req, query, params);
858 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
859 general_params.addLimitAndOffset(query, params);
860 db.query(conn, req, res, query, function (result, conn) {
861 var result = { result: result.rows }
862 includes = [];
863 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
864 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
865 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
866 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
867 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
868 addRelatedData(conn, req, res, result, includes);
869 });
870 });
871 },
873 '/draft': function (conn, req, res, params) {
874 requireAccessLevel(conn, req, res, 'anonymous', function() {
875 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
876 var query = new selector.Selector();
877 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');
878 fields.forEach( function(field) {
879 query.addField(field, null, ['grouped']);
880 });
881 if (req.current_access_level != 'anonymous' || req.current_member_id) {
882 query.addField('draft.author_id');
883 }
884 if (params.draft_id) {
885 query.addWhere('draft.id = ?', params.draft_id);
886 }
887 if (params.current_draft) {
888 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
889 }
890 general_params.addInitiativeOptions(req, query, params);
891 query.addOrderBy('draft.initiative_id, draft.id');
892 general_params.addLimitAndOffset(query, params);
893 db.query(conn, req, res, query, function (result, conn) {
894 var result = { result: result.rows }
895 includes = [];
896 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
897 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
898 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
899 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
900 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
901 addRelatedData(conn, req, res, result, includes);
902 });
903 });
904 },
906 '/suggestion': function (conn, req, res, params) {
907 requireAccessLevel(conn, req, res, 'anonymous', function() {
908 var query = new selector.Selector();
909 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');
910 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
911 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
912 } else {
913 fields.addObjectFields(query, 'suggestion');
914 }
915 general_params.addSuggestionOptions(req, query, params);
916 query.addOrderBy('suggestion.initiative_id, suggestion.id');
917 general_params.addLimitAndOffset(query, params);
918 db.query(conn, req, res, query, function (result, conn) {
919 var result = { result: result.rows }
920 includes = [];
921 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
922 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
923 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
924 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
925 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
926 addRelatedData(conn, req, res, result, includes);
927 });
928 });
929 },
931 '/opinion': function (conn, req, res, params) {
932 requireAccessLevel(conn, req, res, 'pseudonym', function() {
933 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
934 var query = new selector.Selector();
935 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');
936 fields.forEach( function(field) {
937 query.addField(field, null, ['grouped']);
938 });
939 general_params.addMemberOptions(req, query, params);
940 general_params.addSuggestionOptions(req, query, params);
941 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
942 general_params.addLimitAndOffset(query, params);
943 db.query(conn, req, res, query, function (result, conn) {
944 var result = { result: result.rows }
945 includes = [];
946 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
947 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
948 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
949 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
950 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
951 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
952 addRelatedData(conn, req, res, result, includes);
953 });
954 });
955 },
957 '/delegation': function (conn, req, res, params) {
958 requireAccessLevel(conn, req, res, 'pseudonym', function() {
959 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
960 var query = new selector.Selector();
961 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');
962 fields.forEach( function(field) {
963 query.addField(field, null, ['grouped']);
964 });
965 if (params.direction) {
966 switch (params.direction) {
967 case 'in':
968 query.join('member', null, 'member.id = delegation.trustee_id');
969 break;
970 case 'out':
971 query.join('member', null, 'member.id = delegation.truster_id');
972 break;
973 default:
974 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
975 }
976 } else {
977 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
978 }
979 general_params.addMemberOptions(req, query, params);
980 general_params.addIssueOptions(req, query, params);
981 if (params.scope) {
982 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
983 };
984 query.addOrderBy = ['delegation.id'];
985 general_params.addLimitAndOffset(query, params);
986 db.query(conn, req, res, query, function (result, conn) {
987 respond('json', conn, req, res, 'ok', { result: result.rows });
988 });
989 });
990 },
992 '/vote': function (conn, req, res, params) {
993 requireAccessLevel(conn, req, res, 'pseudonym', function() {
994 var query = new selector.Selector();
995 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');
996 query.addField('vote.*');
997 query.addWhere('issue.closed_at NOTNULL');
998 general_params.addMemberOptions(req, query, params);
999 general_params.addInitiativeOptions(req, query, params);
1000 general_params.addLimitAndOffset(query, params);
1001 db.query(conn, req, res, query, function (result, conn) {
1002 respond('json', conn, req, res, 'ok', { result: result.rows });
1003 });
1004 });
1005 },
1007 '/event': function (conn, req, res, params) {
1008 requireAccessLevel(conn, req, res, 'anonymous', function() {
1009 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'];
1010 var query = new selector.Selector();
1011 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');
1012 fields.forEach( function(field) {
1013 query.addField(field, null, ['grouped']);
1014 });
1015 general_params.addMemberOptions(req, query, params);
1016 general_params.addInitiativeOptions(req, query, params);
1017 query.addOrderBy('event.id');
1018 general_params.addLimitAndOffset(query, params);
1019 db.query(conn, req, res, query, function (events, conn) {
1020 var result = { result: events.rows }
1021 includes = [];
1022 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
1023 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
1024 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1025 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1026 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1027 addRelatedData(conn, req, res, result, includes);
1028 });
1029 });
1030 },
1032 // TODO add interfaces for data structure:
1033 // ignored_member requireAccessLevel(conn, req, res, 'member');
1034 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1035 // setting requireAccessLevel(conn, req, res, 'member');
1037 };
1039 // ==========================================================================
1040 // POST methods
1041 // ==========================================================================
1045 exports.post = {
1047 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1048 respond('json', conn, req, res, 'ok', { result: params });
1049 }); },
1051 '/register_test': function (conn, req, res, params) {
1052 var understood = params.understood;
1053 var member_login = randomString(16);
1054 var member_name = params.name;
1055 var member_password = randomString(16);
1056 var member_notify_email = params.email;
1057 var member_notify_email_secret = randomString(24);
1058 var api_key_member = randomString(24);
1059 var api_key_full = randomString(24);
1060 var api_key_pseudonym = randomString(24);
1061 var api_key_anonymous = randomString(24);
1063 if (understood != 'understood') {
1064 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1065 return;
1068 // add member
1069 var query = new selector.SQLInsert('member');
1070 query.addValues({
1071 login: member_login,
1072 password: member_password, // TODO hashing of password
1073 notify_email_unconfirmed: member_notify_email,
1074 notify_email_secret: member_notify_email_secret,
1075 name: member_name
1076 });
1077 query.addReturning('id');
1078 db.query(conn, req, res, query, function (result, conn) {
1079 var member_id = result.rows[0].id;
1081 // add privilege for root unit
1082 var query = new selector.SQLInsert('privilege');
1083 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1084 db.query(conn, req, res, query, function (result, conn) {
1086 var location = params.location;
1087 var unit_id;
1088 switch(location) {
1089 case 'earth':
1090 unit_id = 3;
1091 break;
1092 case 'moon':
1093 unit_id = 4;
1094 break;
1095 case 'mars':
1096 unit_id = 5;
1097 break;
1100 // add privilege for selected planet
1101 var query = new selector.SQLInsert('privilege');
1102 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1103 db.query(conn, req, res, query, function (result, conn) {
1105 // add application key
1106 var query = new selector.SQLInsert('member_application');
1107 query.addValues({
1108 member_id: member_id,
1109 name: 'member',
1110 comment: 'access_level member',
1111 access_level: 'member',
1112 key: api_key_member
1113 });
1114 query.addReturning('id');
1116 db.query(conn, req, res, query, function (result, conn) {
1118 nodemailer.sendmail = '/usr/sbin/sendmail';
1120 // send email to user
1121 nodemailer.send_mail({
1122 sender: config.mail.from,
1123 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1124 to: member_notify_email,
1125 body: "\
1126 Hello " + member_name + ",\n\
1127 \n\
1128 thank you for registering at the public alpha test of the LiquidFeedback\n\
1129 application programming interface. To complete the registration process,\n\
1130 you need to confirm your email address by opening the following URL:\n\
1131 \n\
1132 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1133 \n\
1134 \n\
1135 After you've confirmed your email address, your account will be automatically\n\
1136 activated.\n\
1137 \n\
1138 Your account name is: " + member_name + "\n\
1139 \n\
1140 \n\
1141 You will need the following login and password to register and unregister\n\
1142 applications for your account later. This function is currently not\n\
1143 implemented, but please keep the credentials for future use.\n\
1144 \n\
1145 Account ID: " + member_id + "\n\
1146 Login: " + member_login + "\n\
1147 Password: " + member_password + "\n\
1148 \n\
1149 \n\
1150 To make you able to actually access the API interface, we added the following\n\
1151 application key with full member access privileges to your account:\n\
1152 \n\
1153 API Key: " + api_key_member + "\n\
1154 \n\
1155 \n\
1156 The base address of the public test is: " + config.public_url_path + "\n\
1157 \n\
1158 The programming interface is described in the LiquidFeedback API\n\
1159 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1160 \n\
1161 The current implementation status of lfapi is published at the LiquidFeedback\n\
1162 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1163 \n\
1164 If you have any questions or suggestions, please use our public mailing list\n\
1165 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1166 \n\
1167 For issues regarding your test account, contact us via email at\n\
1168 lqfb-maintainers@public-software-group.org\n\
1169 \n\
1170 \n\
1171 Sincerely,\n\
1172 \n\
1173 Your LiquidFeedback maintainers",
1174 },
1175 function(err, result){
1176 if(err){ console.log(err); }
1177 });
1179 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1180 });
1181 });
1182 });
1183 });
1184 },
1186 /*
1187 '/register': function (conn, req, res, params) {
1188 var invite_key = params.invite_key;
1189 var login = params.login;
1190 var password = params.password;
1191 var name = params.name;
1192 var notify_email = params.notify_email;
1193 if (!invite_key) {
1194 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1195 return;
1196 };
1197 if (!login) {
1198 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1199 return;
1200 };
1201 if (!password) {
1202 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1203 return;
1204 };
1205 if (!name) {
1206 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1207 return;
1208 };
1209 if (!notify_email) {
1210 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1211 return;
1212 };
1213 // check if akey is valid and get member_id for akey
1214 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) {
1215 if (result.rows.length != 1) {
1216 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1217 return;
1218 };
1219 var member_id = result.rows[0].id;
1220 // check if name is available
1221 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1222 if (result.rows.length > 0) {
1223 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1224 return;
1225 };
1226 // check if login is available
1227 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1228 if (result.rows.length > 0) {
1229 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1230 return;
1231 };
1232 var query = { update: 'member', set: { activation: 'now', active: true, } };
1234 });
1235 });
1236 });
1237 },
1238 */
1240 '/session': function (conn, req, res, params) {
1241 var key = params.key;
1242 if (!key) {
1243 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1244 return;
1245 };
1246 var query = new selector.Selector();
1247 query.from('member');
1248 query.join('member_application', null, 'member_application.member_id = member.id');
1249 query.addField('member.id');
1250 query.addWhere(['member.active AND member_application.key = ?', key]);
1251 if (params.interactive) {
1252 query.forUpdateOf('member');
1254 db.query(conn, req, res, query, function (result, conn) {
1255 if (result.rows.length != 1) {
1256 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1257 return;
1258 };
1259 var member_id = result.rows[0].id;
1260 var session_key = randomString(16);
1261 req.sessions[session_key] = member_id;
1262 var query;
1263 if (params.interactive) {
1264 query = new selector.SQLUpdate('member');
1265 query.addWhere(['member.id = ?', member_id]);
1266 query.addValues({ last_activity: 'now' });
1268 db.query(conn, req, res, query, function (result, conn) {
1269 respond('json', conn, req, res, 'ok', { session_key: session_key });
1270 });
1271 });
1272 },
1274 '/member': function (conn, req, res, params) {
1275 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1276 requireAccessLevel(conn, req, res, 'member', function() {
1277 var query = new selector.SQLUpdate('member');
1278 query.addWhere(['member.id = ?', req.current_member_id]);
1279 fields.forEach( function(field) {
1280 if (typeof(params[field]) != 'undefined') {
1281 query.addValues({ field: params[field] });
1282 } else {
1283 query.addValues({ field: null });
1285 });
1286 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1287 });
1288 },
1290 '/membership': function (conn, req, res, params) {
1291 requireAccessLevel(conn, req, res, 'member', function() {
1293 // check if area_id is set
1294 var area_id = parseInt(params.area_id);
1295 if (!area_id) {
1296 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1297 return;
1300 // delete membership
1301 if (params.delete) {
1302 var query;
1303 query = new selector.SQLDelete('membership');
1304 query.addWhere(['area_id = ?', area_id]);
1305 query.addWhere(['member_id = ?', req.current_member_id]);
1306 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1308 // add membership
1309 } else {
1311 // lock member for upsert
1312 lockMemberById(conn, req, res, req.current_member_id, function() {
1314 // check and lock privilege
1315 requireAreaPrivilege(conn, req, res, area_id, function() {
1317 // upsert membership
1318 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1319 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1320 db.query(conn, req, res, query, function(result) {
1321 respond('json', conn, req, res, 'ok');
1322 });
1323 });
1324 });
1326 });
1327 },
1329 '/interest': function (conn, req, res, params) {
1330 requireAccessLevel(conn, req, res, 'member', function() {
1331 var query;
1333 // check if issue_id is set
1334 var issue_id = parseInt(params.issue_id);
1335 if (!issue_id) {
1336 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1337 return;
1340 // lock member for upsert
1341 lockMemberById(conn, req, res, req.current_member_id, function() {
1343 // delete interest
1344 if (params.delete) {
1346 // check issue state
1347 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1349 // delete interest
1350 query = new selector.SQLDelete('interest');
1351 query.addWhere(['issue_id = ?', issue_id]);
1352 query.addWhere(['member_id = ?', req.current_member_id]);
1353 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1354 });
1356 // add interest
1357 } else {
1359 // check and lock privilege
1360 requireIssuePrivilege(conn, req, res, issue_id, function() {
1362 // check issue state
1363 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1365 // upsert interest
1366 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1367 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1368 db.query(conn, req, res, query, function(result) {
1369 respond('json', conn, req, res, 'ok');
1370 });
1371 });
1372 });
1373 };
1374 });
1375 });
1376 },
1378 '/issue_comment': function (conn, req, res, params) {
1379 requireAccessLevel(conn, req, res, 'member', function() {
1381 var issue_id = parseInt(params.issue_id);
1382 var formatting_engine = params.formatting_engine
1383 var content = params.content;
1385 if (!issue_id) {
1386 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1387 return;
1390 // delete issue comment
1391 if (params.delete) {
1392 var query;
1393 query = new selector.SQLDelete('issue_comment');
1394 query.addWhere(['issue_id = ?', params.issue_id]);
1395 query.addWhere(['member_id = ?', req.current_member_id]);
1396 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1398 // upsert issue comment
1399 } else {
1401 // check if formatting engine is supplied and valid
1402 if (!formatting_engine) {
1403 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1404 return;
1405 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1406 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1407 return;
1408 };
1410 // check if content is supplied
1411 if (!content) {
1412 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1413 return;
1416 // lock member for upsert
1417 lockMemberById(conn, req, res, req.current_member_id, function() {
1419 // check and lock privilege
1420 requireIssuePrivilege(conn, req, res, issue_id, function() {
1422 // upsert issue comment
1423 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1424 query.addValues({
1425 issue_id: issue_id,
1426 member_id: req.current_member_id,
1427 changed: 'now',
1428 formatting_engine: formatting_engine,
1429 content: content
1430 });
1432 db.query(conn, req, res, query, function(result) {
1433 respond('json', conn, req, res, 'ok');
1434 });
1436 });
1437 });
1441 });
1442 },
1444 '/voting_comment': function (conn, req, res, params) {
1445 requireAccessLevel(conn, req, res, 'member', function() {
1447 var issue_id = parseInt(params.issue_id);
1448 var formatting_engine = params.formatting_engine
1449 var content = params.content;
1451 if (!issue_id) {
1452 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1453 return;
1457 // delete voting comment
1458 if (params.delete) {
1459 var query;
1460 query = new selector.SQLDelete('voting_comment');
1461 query.addWhere(['issue_id = ?', params.issue_id]);
1462 query.addWhere(['member_id = ?', req.current_member_id]);
1463 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1465 // upsert voting comment
1466 } else {
1468 // check if formatting engine is supplied and valid
1469 if (!formatting_engine) {
1470 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1471 return;
1472 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1473 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1474 return;
1475 };
1477 // check if content is supplied
1478 if (!content) {
1479 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1480 return;
1483 // lock member for upsert
1484 lockMemberById(conn, req, res, req.current_member_id, function() {
1486 // check and lock privilege
1487 requireIssuePrivilege(conn, req, res, issue_id, function() {
1489 // check issue state
1490 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1492 // upsert voting comment
1493 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1494 query.addValues({
1495 issue_id: issue_id,
1496 member_id: req.current_member_id,
1497 changed: 'now',
1498 formatting_engine: formatting_engine,
1499 content: content
1500 });
1502 db.query(conn, req, res, query, function(result) {
1503 respond('json', conn, req, res, 'ok');
1504 });
1506 });
1507 });
1508 })
1509 };
1510 });
1511 },
1513 '/supporter': function (conn, req, res, params) {
1514 requireAccessLevel(conn, req, res, 'member', function() {
1515 var initiative_id = parseInt(params.initiative_id);
1516 var draft_id = parseInt(params.draft_id);
1518 // check if needed arguments are supplied
1519 if (!initiative_id) {
1520 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1521 return;
1524 if (!draft_id) {
1525 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1526 return;
1529 // lock member for upsert
1530 lockMemberById(conn, req, res, req.current_member_id, function() {
1532 // delete supporter
1533 if (params.delete) {
1535 // check issue state
1536 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1538 // delete supporter
1539 var query = new selector.SQLDelete('supporter');
1540 query.addWhere(['initiative_id = ?', initiative_id]);
1541 query.addWhere(['member_id = ?', req.current_member_id]);
1542 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1544 });
1546 // upsert supporter
1547 } else {
1549 // check and lock privilege
1550 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1552 // check issue state
1553 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1555 // check if given draft is the current one
1556 var query = new selector.Selector('current_draft');
1557 query.addField('NULL');
1558 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1559 query.addWhere(['current_draft.id = ?', draft_id]);
1561 db.query(conn, req, res, query, function(result) {
1562 if (result.rows.length != 1) {
1563 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1564 return;
1567 // upsert supporter
1568 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1569 query.addValues({
1570 initiative_id: initiative_id,
1571 member_id: req.current_member_id,
1572 draft_id: draft_id
1573 });
1575 db.query(conn, req, res, query, function(result) {
1576 respond('json', conn, req, res, 'ok');
1577 });
1579 });
1580 });
1581 });
1582 };
1583 });
1584 });
1585 },
1587 '/draft': function (conn, req, res, params) {
1588 requireAccessLevel(conn, req, res, 'member', function() {
1589 var area_id = parseInt(params.area_id);
1590 var policy_id = parseInt(params.policy_id);
1591 var issue_id = parseInt(params.issue_id);
1592 var initiative_id = parseInt(params.initiative_id);
1593 var initiative_name = params.initiative_name;
1594 var initiative_discussion_url = params.initiative_discussion_url;
1595 var formatting_engine = params.formatting_engine;
1596 var content = params.content;
1598 if (!initiative_discussion_url) initiative_discussion_url = null;
1600 // check parameters
1601 if (!formatting_engine) {
1602 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1603 return;
1604 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1605 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1606 return;
1607 };
1609 if (!content) {
1610 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1611 return;
1612 };
1614 lockMemberById(conn, req, res, req.current_member_id, function() {
1616 // new draft in new initiative in new issue
1617 if (area_id && !issue_id && !initiative_id) {
1619 // check parameters for new issue
1620 if (!policy_id) {
1621 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1622 return;
1625 if (!initiative_name) {
1626 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1627 return;
1630 requireAreaPrivilege(conn, req, res, area_id, function() {
1632 // check if policy is allowed in this area and if area and policy are active
1633 var query = new selector.Selector();
1634 query.from('allowed_policy');
1635 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1636 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1637 query.addField('NULL');
1638 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1639 db.query(conn, req, res, query, function (result, conn) {
1640 if (result.rows.length != 1) {
1641 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.');
1642 return;
1643 };
1645 // check contingent
1646 requireContingentLeft(conn, req, res, true, function() {
1648 // insert new issue
1649 var query = new selector.SQLInsert('issue');
1650 query.addValues({
1651 area_id: area_id,
1652 policy_id: policy_id
1653 });
1654 query.addReturning('id');
1655 db.query(conn, req, res, query, function(result) {
1656 var issue_id = result.rows[0].id;
1658 // insert new initiative
1659 var query = new selector.SQLInsert('initiative');
1660 query.addValues({
1661 issue_id: issue_id,
1662 name: initiative_name,
1663 discussion_url: initiative_discussion_url
1664 });
1665 query.addReturning('id');
1666 db.query(conn, req, res, query, function(result) {
1667 var initiative_id = result.rows[0].id;
1669 // insert initiator
1670 var query = new selector.SQLInsert('initiator');
1671 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1672 db.query(conn, req, res, query, function(result) {
1674 // insert new draft
1675 var query = new selector.SQLInsert('draft');
1676 query.addValues({
1677 initiative_id: initiative_id,
1678 author_id: req.current_member_id,
1679 formatting_engine: formatting_engine,
1680 content: content
1681 });
1682 query.addReturning('id');
1683 db.query(conn, req, res, query, function (result, conn) {
1684 var draft_id = result.rows[0].id;
1686 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1687 });
1688 });
1689 });
1690 });
1691 });
1692 });
1693 });
1695 // new draft in new initiative in existant issue
1696 } else if (issue_id && !area_id && !initiative_id) {
1698 if (!initiative_name) {
1699 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1700 return;
1703 // check privilege
1704 requireIssuePrivilege(conn, req, res, issue_id, function() {
1706 // check issue state
1707 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1709 // check contingent
1710 requireContingentLeft(conn, req, res, true, function() {
1712 // insert initiative
1713 var query = new selector.SQLInsert('initiative');
1714 query.addValues({
1715 issue_id: issue_id,
1716 name: initiative_name,
1717 discussion_url: initiative_discussion_url
1718 });
1719 query.addReturning('id');
1720 db.query(conn, req, res, query, function(result) {
1721 var initiative_id = result.rows[0].id;
1723 // insert initiator
1724 var query = new selector.SQLInsert('initiator');
1725 query.addValues({
1726 initiative_id: initiative_id,
1727 member_id: req.current_member_id,
1728 accepted: true
1729 });
1730 db.query(conn, req, res, query, function(result) {
1732 // insert draft
1733 var query = new selector.SQLInsert('draft');
1734 query.addValues({
1735 initiative_id: initiative_id,
1736 author_id: req.current_member_id,
1737 formatting_engine: formatting_engine,
1738 content: content
1739 });
1740 query.addReturning('id');
1741 db.query(conn, req, res, query, function (result, conn) {
1743 var draft_id = result.rows[0].id;
1744 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1746 });
1747 });
1748 });
1749 });
1750 });
1751 });
1753 // new draft in existant initiative
1754 } else if (initiative_id && !area_id && !issue_id ) {
1756 // check privilege
1757 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1759 // check issue state
1760 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1763 // get initiator
1764 var query = new selector.Selector();
1765 query.from('initiator');
1766 query.addField('accepted');
1767 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1768 db.query(conn, req, res, query, function (result, conn) {
1770 // if member is not initiator, deny creating new draft
1771 if (result.rows.length != 1) {
1772 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1773 return;
1775 var initiator = result.rows[0];
1776 if (!initiator.accepted) {
1777 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.');
1778 return;
1779 };
1781 // check contingent
1782 requireContingentLeft(conn, req, res, false, function() {
1784 // insert new draft
1785 var query = new selector.SQLInsert('draft');
1786 query.addValues({
1787 initiative_id: initiative_id,
1788 author_id: req.current_member_id,
1789 formatting_engine: formatting_engine,
1790 content: content
1791 });
1792 query.addReturning('id');
1793 db.query(conn, req, res, query, function (result, conn) {
1795 var draft_id = result.rows[0].id;
1796 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1797 });
1798 });
1799 });
1800 });
1801 });
1803 // none of them (invalid request)
1804 } else {
1805 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1806 };
1808 });
1809 });
1810 },
1812 '/suggestion': function (conn, req, res, params) {
1813 requireAccessLevel(conn, req, res, 'member', function() {
1814 // TODO
1815 });
1816 },
1818 '/opinion': function (conn, req, res, params) {
1819 requireAccessLevel(conn, req, res, 'member', function() {
1820 // TODO
1821 });
1822 },
1824 '/delegation': function (conn, req, res, params) {
1825 requireAccessLevel(conn, req, res, 'member', function() {
1826 var unit_id = parseInt(params.unit_id);
1827 var area_id = parseInt(params.area_id);
1828 var issue_id = parseInt(params.issue_id);
1829 var trustee_id;
1831 if (params.trustee_id == '') {
1832 trustee_id = null;
1833 } else {
1834 trustee_id = parseInt(params.trustee_id);
1837 lockMemberById(conn, req, res, req.current_member_id, function() {
1839 if (params.delete) {
1840 var query = new selector.SQLDelete('delegation')
1841 if (unit_id && !area_id && !issue_id) {
1842 query.addWhere(['unit_id = ?', unit_id]);
1843 } else if (!unit_id && area_id && !issue_id) {
1844 query.addWhere(['area_id = ?', area_id]);
1845 } else if (!unit_id && !area_id && issue_id) {
1846 query.addWhere(['issue_id = ?', issue_id]);
1847 } else {
1848 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1849 return;
1851 query.addWhere(['truster_id = ?', req.current_member_id]);
1852 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1853 } else {
1854 var query = new selector.Upserter('delegation', ['truster_id']);
1855 query.addValues({
1856 truster_id: req.current_member_id,
1857 trustee_id: trustee_id
1858 });
1859 if (unit_id && !area_id && !issue_id) {
1861 // check privilege
1862 requireUnitPrivilege(conn, req, res, unit_id, function() {
1864 query.addKeys(['unit_id'])
1865 query.addValues({ unit_id: unit_id, scope: 'unit' });
1866 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1867 });
1869 } else if (!unit_id && area_id && !issue_id) {
1871 // check privilege
1872 requireAreaPrivilege(conn, req, res, area_id, function() {
1874 query.addKeys(['area_id'])
1875 query.addValues({ area_id: area_id, scope: 'area' });
1876 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1877 });
1879 } else if (!unit_id && !area_id && issue_id) {
1881 // check privilege
1882 requireIssuePrivilege(conn, req, res, issue_id, function() {
1884 // check issue state
1885 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1887 query.addKeys(['issue_id'])
1888 query.addValues({ issue_id: issue_id, scope: 'issue' });
1889 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1890 });
1891 });
1892 } else {
1893 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1894 return;
1898 });
1900 });
1901 },
1903 };

Impressum / About Us