lfapi

view lfapi/main.js @ 15:ef5f746f21ad

Added GET /population
author bsw
date Sat Nov 05 12:14:18 2011 +0100 (2011-11-05)
parents fe484d4ca81f
children 97103921ff3c
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 query.from('direct_population_snapshot', 'population');
654 switch (params.snapshot) {
655 case 'latest':
656 query.addWhere('population.event = issue.latest_snapshot_event');
657 break;
659 case 'end_of_admission':
660 case 'half_freeze':
661 case 'full_freeze':
662 query.addWhere(['population.event = ?', params.snapshot]);
663 break;
665 default:
666 respond('json', conn, req, res, 'unprocessable', { error: 'Invalid snapshot type' });
667 return;
669 };
670 query.addField('population.*');
671 query.join('member', null, 'member.id = population.member_id');
672 query.join('issue', null, 'population.issue_id = issue.id');
673 query.join('policy', null, 'policy.id = issue.policy_id');
674 query.join('area', null, 'area.id = issue.area_id');
675 query.join('unit', null, 'area.unit_id = unit.id');
676 general_params.addMemberOptions(req, query, params);
677 general_params.addIssueOptions(req, query, params);
678 query.addOrderBy('population.issue_id, population.member_id');
679 general_params.addLimitAndOffset(query, params);
680 db.query(conn, req, res, query, function (population_result, conn) {
681 console.log(population_result);
682 var result = { result: population_result.rows }
683 includes = [];
684 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
685 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
686 if (params.include_areas) includes.push({ class: 'area', objects: 'areas'});
687 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
688 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
689 addRelatedData(conn, req, res, result, includes);
690 });
691 });
692 },
694 '/interest': function (conn, req, res, params) {
695 requireAccessLevel(conn, req, res, 'pseudonym', function() {
696 var query = new selector.Selector();
697 switch (params.snapshot) {
698 case 'latest':
699 query.from('direct_interest_snapshot', 'interest');
700 query.addWhere('interest.event = issue.latest_snapshot_event');
701 break;
703 case 'end_of_admission':
704 case 'half_freeze':
705 case 'full_freeze':
706 query.from('direct_interest_snapshot', 'interest');
707 query.addWhere('interest.event = ?', params.snapshot);
708 break;
710 case undefined:
711 query.from('interest');
712 break;
714 default:
715 respond('json', conn, req, res, 'unprocessable', { error: 'Invalid snapshot type' });
716 return;
718 };
719 query.addField('interest.*');
720 query.join('member', null, 'member.id = interest.member_id');
721 query.join('issue', null, 'interest.issue_id = issue.id');
722 query.join('policy', null, 'policy.id = issue.policy_id');
723 query.join('area', null, 'area.id = issue.area_id');
724 query.join('unit', null, 'area.unit_id = unit.id');
725 general_params.addMemberOptions(req, query, params);
726 general_params.addIssueOptions(req, query, params);
727 query.addOrderBy('interest.issue_id, interest.member_id');
728 general_params.addLimitAndOffset(query, params);
729 db.query(conn, req, res, query, function (interest_result, conn) {
730 var result = { result: interest_result.rows }
731 includes = [];
732 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
733 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
734 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
735 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
736 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
737 addRelatedData(conn, req, res, result, includes);
738 });
739 });
740 },
742 '/issue_comment': function (conn, req, res, params) {
743 requireAccessLevel(conn, req, res, 'pseudonym', function() {
744 var query = new selector.Selector();
745 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');
746 query.addField('issue_comment.*');
747 general_params.addMemberOptions(req, query, params);
748 general_params.addIssueOptions(req, query, params);
749 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
750 general_params.addLimitAndOffset(query, params);
751 db.query(conn, req, res, query, function (issue_comment_result, conn) {
752 var result = { result: issue_comment_result.rows }
753 includes = [];
754 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
755 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
756 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
757 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
758 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
759 addRelatedData(conn, req, res, result, includes);
760 });
761 });
762 },
764 '/initiative': function (conn, req, res, params) {
765 requireAccessLevel(conn, req, res, 'anonymous', function() {
766 var query = new selector.Selector();
767 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');
768 fields.addObjectFields(query, 'initiative');
769 query.addOrderBy('initiative.id');
770 general_params.addInitiativeOptions(req, query, params);
771 general_params.addLimitAndOffset(query, params);
772 db.query(conn, req, res, query, function (initiative_result, conn) {
773 var result = { result: initiative_result.rows }
774 includes = [];
775 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
776 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
777 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
778 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
779 addRelatedData(conn, req, res, result, includes);
780 });
781 });
782 },
784 '/initiator': function (conn, req, res, params) {
785 requireAccessLevel(conn, req, res, 'pseudonym', function() {
786 var fields = ['initiator.initiative_id', 'initiator.member_id'];
787 var query = new selector.Selector();
788 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');
789 query.addWhere('initiator.accepted');
790 fields.forEach( function(field) {
791 query.addField(field, null, ['grouped']);
792 });
793 general_params.addMemberOptions(req, query, params);
794 general_params.addInitiativeOptions(req, query, params);
795 query.addOrderBy('initiator.initiative_id, initiator.member_id');
796 general_params.addLimitAndOffset(query, params);
797 db.query(conn, req, res, query, function (initiator, conn) {
798 var result = { result: initiator.rows }
799 includes = [];
800 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
801 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
802 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
803 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
804 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
805 addRelatedData(conn, req, res, result, includes);
806 });
807 });
808 },
811 '/supporter': function (conn, req, res, params) {
812 requireAccessLevel(conn, req, res, 'pseudonym', function() {
813 var fields = ['supporter.issue_id', 'supporter.initiative_id', 'supporter.member_id', 'supporter.draft_id'];
814 var query = new selector.Selector();
815 query.from('supporter')
816 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');
817 fields.forEach( function(field) {
818 query.addField(field, null, ['grouped']);
819 });
820 general_params.addMemberOptions(req, query, params);
821 general_params.addInitiativeOptions(req, query, params);
822 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
823 general_params.addLimitAndOffset(query, params);
824 db.query(conn, req, res, query, function (supporter, conn) {
825 var result = { result: supporter.rows }
826 includes = [];
827 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
828 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
829 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
830 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
831 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
832 addRelatedData(conn, req, res, result, includes);
833 });
834 });
835 },
837 '/battle': function (conn, req, res, params) {
838 requireAccessLevel(conn, req, res, 'anonymous', function() {
839 var query = new selector.Selector();
840 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');
841 query.addField('battle.*');
842 general_params.addInitiativeOptions(req, query, params);
843 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
844 general_params.addLimitAndOffset(query, params);
845 db.query(conn, req, res, query, function (result, conn) {
846 var result = { result: result.rows }
847 includes = [];
848 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
849 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
850 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
851 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
852 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
853 addRelatedData(conn, req, res, result, includes);
854 });
855 });
856 },
858 '/draft': function (conn, req, res, params) {
859 requireAccessLevel(conn, req, res, 'anonymous', function() {
860 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
861 var query = new selector.Selector();
862 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');
863 fields.forEach( function(field) {
864 query.addField(field, null, ['grouped']);
865 });
866 if (req.current_access_level != 'anonymous' || req.current_member_id) {
867 query.addField('draft.author_id');
868 }
869 if (params.draft_id) {
870 query.addWhere('draft.id = ?', params.draft_id);
871 }
872 if (params.current_draft) {
873 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
874 }
875 general_params.addInitiativeOptions(req, query, params);
876 query.addOrderBy('draft.initiative_id, draft.id');
877 general_params.addLimitAndOffset(query, params);
878 db.query(conn, req, res, query, function (result, conn) {
879 var result = { result: result.rows }
880 includes = [];
881 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
882 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
883 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
884 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
885 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
886 addRelatedData(conn, req, res, result, includes);
887 });
888 });
889 },
891 '/suggestion': function (conn, req, res, params) {
892 requireAccessLevel(conn, req, res, 'anonymous', function() {
893 var query = new selector.Selector();
894 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');
895 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
896 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
897 } else {
898 fields.addObjectFields(query, 'suggestion');
899 }
900 general_params.addSuggestionOptions(req, query, params);
901 query.addOrderBy('suggestion.initiative_id, suggestion.id');
902 general_params.addLimitAndOffset(query, params);
903 db.query(conn, req, res, query, function (result, conn) {
904 var result = { result: result.rows }
905 includes = [];
906 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
907 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
908 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
909 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
910 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
911 addRelatedData(conn, req, res, result, includes);
912 });
913 });
914 },
916 '/opinion': function (conn, req, res, params) {
917 requireAccessLevel(conn, req, res, 'pseudonym', function() {
918 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
919 var query = new selector.Selector();
920 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');
921 fields.forEach( function(field) {
922 query.addField(field, null, ['grouped']);
923 });
924 general_params.addMemberOptions(req, query, params);
925 general_params.addSuggestionOptions(req, query, params);
926 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
927 general_params.addLimitAndOffset(query, params);
928 db.query(conn, req, res, query, function (result, conn) {
929 var result = { result: result.rows }
930 includes = [];
931 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
932 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
933 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
934 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
935 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
936 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
937 addRelatedData(conn, req, res, result, includes);
938 });
939 });
940 },
942 '/delegation': function (conn, req, res, params) {
943 requireAccessLevel(conn, req, res, 'pseudonym', function() {
944 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
945 var query = new selector.Selector();
946 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');
947 fields.forEach( function(field) {
948 query.addField(field, null, ['grouped']);
949 });
950 if (params.direction) {
951 switch (params.direction) {
952 case 'in':
953 query.join('member', null, 'member.id = delegation.trustee_id');
954 break;
955 case 'out':
956 query.join('member', null, 'member.id = delegation.truster_id');
957 break;
958 default:
959 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
960 }
961 } else {
962 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
963 }
964 general_params.addMemberOptions(req, query, params);
965 general_params.addIssueOptions(req, query, params);
966 if (params.scope) {
967 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
968 };
969 query.addOrderBy = ['delegation.id'];
970 general_params.addLimitAndOffset(query, params);
971 db.query(conn, req, res, query, function (result, conn) {
972 respond('json', conn, req, res, 'ok', { result: result.rows });
973 });
974 });
975 },
977 '/vote': function (conn, req, res, params) {
978 requireAccessLevel(conn, req, res, 'pseudonym', function() {
979 var query = new selector.Selector();
980 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');
981 query.addField('vote.*');
982 query.addWhere('issue.closed_at NOTNULL');
983 general_params.addMemberOptions(req, query, params);
984 general_params.addInitiativeOptions(req, query, params);
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 '/event': function (conn, req, res, params) {
993 requireAccessLevel(conn, req, res, 'anonymous', function() {
994 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'];
995 var query = new selector.Selector();
996 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');
997 fields.forEach( function(field) {
998 query.addField(field, null, ['grouped']);
999 });
1000 general_params.addMemberOptions(req, query, params);
1001 general_params.addInitiativeOptions(req, query, params);
1002 query.addOrderBy('event.id');
1003 general_params.addLimitAndOffset(query, params);
1004 db.query(conn, req, res, query, function (events, conn) {
1005 var result = { result: events.rows }
1006 includes = [];
1007 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
1008 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
1009 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1010 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1011 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1012 addRelatedData(conn, req, res, result, includes);
1013 });
1014 });
1015 },
1017 // TODO add interfaces for data structure:
1018 // ignored_member requireAccessLevel(conn, req, res, 'member');
1019 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1020 // setting requireAccessLevel(conn, req, res, 'member');
1022 };
1024 // ==========================================================================
1025 // POST methods
1026 // ==========================================================================
1030 exports.post = {
1032 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1033 respond('json', conn, req, res, 'ok', { result: params });
1034 }); },
1036 '/register_test': function (conn, req, res, params) {
1037 var understood = params.understood;
1038 var member_login = randomString(16);
1039 var member_name = params.name;
1040 var member_password = randomString(16);
1041 var member_notify_email = params.email;
1042 var member_notify_email_secret = randomString(24);
1043 var api_key_member = randomString(24);
1044 var api_key_full = randomString(24);
1045 var api_key_pseudonym = randomString(24);
1046 var api_key_anonymous = randomString(24);
1048 if (understood != 'understood') {
1049 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1050 return;
1053 // add member
1054 var query = new selector.SQLInsert('member');
1055 query.addValues({
1056 login: member_login,
1057 password: member_password, // TODO hashing of password
1058 notify_email_unconfirmed: member_notify_email,
1059 notify_email_secret: member_notify_email_secret,
1060 name: member_name
1061 });
1062 query.addReturning('id');
1063 db.query(conn, req, res, query, function (result, conn) {
1064 var member_id = result.rows[0].id;
1066 // add privilege for root unit
1067 var query = new selector.SQLInsert('privilege');
1068 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1069 db.query(conn, req, res, query, function (result, conn) {
1071 var location = params.location;
1072 var unit_id;
1073 switch(location) {
1074 case 'earth':
1075 unit_id = 3;
1076 break;
1077 case 'moon':
1078 unit_id = 4;
1079 break;
1080 case 'mars':
1081 unit_id = 5;
1082 break;
1085 // add privilege for selected planet
1086 var query = new selector.SQLInsert('privilege');
1087 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1088 db.query(conn, req, res, query, function (result, conn) {
1090 // add application key
1091 var query = new selector.SQLInsert('member_application');
1092 query.addValues({
1093 member_id: member_id,
1094 name: 'member',
1095 comment: 'access_level member',
1096 access_level: 'member',
1097 key: api_key_member
1098 });
1099 query.addReturning('id');
1101 db.query(conn, req, res, query, function (result, conn) {
1103 nodemailer.sendmail = '/usr/sbin/sendmail';
1105 // send email to user
1106 nodemailer.send_mail({
1107 sender: config.mail.from,
1108 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1109 to: member_notify_email,
1110 body: "\
1111 Hello " + member_name + ",\n\
1112 \n\
1113 thank you for registering at the public alpha test of the LiquidFeedback\n\
1114 application programming interface. To complete the registration process,\n\
1115 you need to confirm your email address by opening the following URL:\n\
1116 \n\
1117 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1118 \n\
1119 \n\
1120 After you've confirmed your email address, your account will be automatically\n\
1121 activated.\n\
1122 \n\
1123 Your account name is: " + member_name + "\n\
1124 \n\
1125 \n\
1126 You will need the following login and password to register and unregister\n\
1127 applications for your account later. This function is currently not\n\
1128 implemented, but please keep the credentials for future use.\n\
1129 \n\
1130 Account ID: " + member_id + "\n\
1131 Login: " + member_login + "\n\
1132 Password: " + member_password + "\n\
1133 \n\
1134 \n\
1135 To make you able to actually access the API interface, we added the following\n\
1136 application key with full member access privileges to your account:\n\
1137 \n\
1138 API Key: " + api_key_member + "\n\
1139 \n\
1140 \n\
1141 The base address of the public test is: " + config.public_url_path + "\n\
1142 \n\
1143 The programming interface is described in the LiquidFeedback API\n\
1144 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1145 \n\
1146 The current implementation status of lfapi is published at the LiquidFeedback\n\
1147 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1148 \n\
1149 If you have any questions or suggestions, please use our public mailing list\n\
1150 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1151 \n\
1152 For issues regarding your test account, contact us via email at\n\
1153 lqfb-maintainers@public-software-group.org\n\
1154 \n\
1155 \n\
1156 Sincerely,\n\
1157 \n\
1158 Your LiquidFeedback maintainers",
1159 },
1160 function(err, result){
1161 if(err){ console.log(err); }
1162 });
1164 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1165 });
1166 });
1167 });
1168 });
1169 },
1171 /*
1172 '/register': function (conn, req, res, params) {
1173 var invite_key = params.invite_key;
1174 var login = params.login;
1175 var password = params.password;
1176 var name = params.name;
1177 var notify_email = params.notify_email;
1178 if (!invite_key) {
1179 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1180 return;
1181 };
1182 if (!login) {
1183 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1184 return;
1185 };
1186 if (!password) {
1187 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1188 return;
1189 };
1190 if (!name) {
1191 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1192 return;
1193 };
1194 if (!notify_email) {
1195 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1196 return;
1197 };
1198 // check if akey is valid and get member_id for akey
1199 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) {
1200 if (result.rows.length != 1) {
1201 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1202 return;
1203 };
1204 var member_id = result.rows[0].id;
1205 // check if name is available
1206 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1207 if (result.rows.length > 0) {
1208 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1209 return;
1210 };
1211 // check if login is available
1212 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1213 if (result.rows.length > 0) {
1214 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1215 return;
1216 };
1217 var query = { update: 'member', set: { activation: 'now', active: true, } };
1219 });
1220 });
1221 });
1222 },
1223 */
1225 '/session': function (conn, req, res, params) {
1226 var key = params.key;
1227 if (!key) {
1228 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1229 return;
1230 };
1231 var query = new selector.Selector();
1232 query.from('member');
1233 query.join('member_application', null, 'member_application.member_id = member.id');
1234 query.addField('member.id');
1235 query.addWhere(['member.active AND member_application.key = ?', key]);
1236 if (params.interactive) {
1237 query.forUpdateOf('member');
1239 db.query(conn, req, res, query, function (result, conn) {
1240 if (result.rows.length != 1) {
1241 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1242 return;
1243 };
1244 var member_id = result.rows[0].id;
1245 var session_key = randomString(16);
1246 req.sessions[session_key] = member_id;
1247 var query;
1248 if (params.interactive) {
1249 query = new selector.SQLUpdate('member');
1250 query.addWhere(['member.id = ?', member_id]);
1251 query.addValues({ last_activity: 'now' });
1253 db.query(conn, req, res, query, function (result, conn) {
1254 respond('json', conn, req, res, 'ok', { session_key: session_key });
1255 });
1256 });
1257 },
1259 '/member': function (conn, req, res, params) {
1260 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1261 requireAccessLevel(conn, req, res, 'member', function() {
1262 var query = new selector.SQLUpdate('member');
1263 query.addWhere(['member.id = ?', req.current_member_id]);
1264 fields.forEach( function(field) {
1265 if (typeof(params[field]) != 'undefined') {
1266 query.addValues({ field: params[field] });
1267 } else {
1268 query.addValues({ field: null });
1270 });
1271 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1272 });
1273 },
1275 '/membership': function (conn, req, res, params) {
1276 requireAccessLevel(conn, req, res, 'member', function() {
1278 // check if area_id is set
1279 var area_id = parseInt(params.area_id);
1280 if (!area_id) {
1281 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1282 return;
1285 // delete membership
1286 if (params.delete) {
1287 var query;
1288 query = new selector.SQLDelete('membership');
1289 query.addWhere(['area_id = ?', area_id]);
1290 query.addWhere(['member_id = ?', req.current_member_id]);
1291 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1293 // add membership
1294 } else {
1296 // lock member for upsert
1297 lockMemberById(conn, req, res, req.current_member_id, function() {
1299 // check and lock privilege
1300 requireAreaPrivilege(conn, req, res, area_id, function() {
1302 // upsert membership
1303 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1304 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1305 db.query(conn, req, res, query, function(result) {
1306 respond('json', conn, req, res, 'ok');
1307 });
1308 });
1309 });
1311 });
1312 },
1314 '/interest': function (conn, req, res, params) {
1315 requireAccessLevel(conn, req, res, 'member', function() {
1316 var query;
1318 // check if issue_id is set
1319 var issue_id = parseInt(params.issue_id);
1320 if (!issue_id) {
1321 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1322 return;
1325 // lock member for upsert
1326 lockMemberById(conn, req, res, req.current_member_id, function() {
1328 // delete interest
1329 if (params.delete) {
1331 // check issue state
1332 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1334 // delete interest
1335 query = new selector.SQLDelete('interest');
1336 query.addWhere(['issue_id = ?', issue_id]);
1337 query.addWhere(['member_id = ?', req.current_member_id]);
1338 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1339 });
1341 // add interest
1342 } else {
1344 // check and lock privilege
1345 requireIssuePrivilege(conn, req, res, issue_id, function() {
1347 // check issue state
1348 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1350 // upsert interest
1351 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1352 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1353 db.query(conn, req, res, query, function(result) {
1354 respond('json', conn, req, res, 'ok');
1355 });
1356 });
1357 });
1358 };
1359 });
1360 });
1361 },
1363 '/issue_comment': function (conn, req, res, params) {
1364 requireAccessLevel(conn, req, res, 'member', function() {
1366 var issue_id = parseInt(params.issue_id);
1367 var formatting_engine = params.formatting_engine
1368 var content = params.content;
1370 if (!issue_id) {
1371 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1372 return;
1375 // delete issue comment
1376 if (params.delete) {
1377 var query;
1378 query = new selector.SQLDelete('issue_comment');
1379 query.addWhere(['issue_id = ?', params.issue_id]);
1380 query.addWhere(['member_id = ?', req.current_member_id]);
1381 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1383 // upsert issue comment
1384 } else {
1386 // check if formatting engine is supplied and valid
1387 if (!formatting_engine) {
1388 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1389 return;
1390 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1391 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1392 return;
1393 };
1395 // check if content is supplied
1396 if (!content) {
1397 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1398 return;
1401 // lock member for upsert
1402 lockMemberById(conn, req, res, req.current_member_id, function() {
1404 // check and lock privilege
1405 requireIssuePrivilege(conn, req, res, issue_id, function() {
1407 // upsert issue comment
1408 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1409 query.addValues({
1410 issue_id: issue_id,
1411 member_id: req.current_member_id,
1412 changed: 'now',
1413 formatting_engine: formatting_engine,
1414 content: content
1415 });
1417 db.query(conn, req, res, query, function(result) {
1418 respond('json', conn, req, res, 'ok');
1419 });
1421 });
1422 });
1426 });
1427 },
1429 '/voting_comment': function (conn, req, res, params) {
1430 requireAccessLevel(conn, req, res, 'member', function() {
1432 var issue_id = parseInt(params.issue_id);
1433 var formatting_engine = params.formatting_engine
1434 var content = params.content;
1436 if (!issue_id) {
1437 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1438 return;
1442 // delete voting comment
1443 if (params.delete) {
1444 var query;
1445 query = new selector.SQLDelete('voting_comment');
1446 query.addWhere(['issue_id = ?', params.issue_id]);
1447 query.addWhere(['member_id = ?', req.current_member_id]);
1448 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1450 // upsert voting comment
1451 } else {
1453 // check if formatting engine is supplied and valid
1454 if (!formatting_engine) {
1455 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1456 return;
1457 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1458 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1459 return;
1460 };
1462 // check if content is supplied
1463 if (!content) {
1464 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1465 return;
1468 // lock member for upsert
1469 lockMemberById(conn, req, res, req.current_member_id, function() {
1471 // check and lock privilege
1472 requireIssuePrivilege(conn, req, res, issue_id, function() {
1474 // check issue state
1475 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1477 // upsert voting comment
1478 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1479 query.addValues({
1480 issue_id: issue_id,
1481 member_id: req.current_member_id,
1482 changed: 'now',
1483 formatting_engine: formatting_engine,
1484 content: content
1485 });
1487 db.query(conn, req, res, query, function(result) {
1488 respond('json', conn, req, res, 'ok');
1489 });
1491 });
1492 });
1493 })
1494 };
1495 });
1496 },
1498 '/supporter': function (conn, req, res, params) {
1499 requireAccessLevel(conn, req, res, 'member', function() {
1500 var initiative_id = parseInt(params.initiative_id);
1501 var draft_id = parseInt(params.draft_id);
1503 // check if needed arguments are supplied
1504 if (!initiative_id) {
1505 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1506 return;
1509 if (!draft_id) {
1510 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1511 return;
1514 // lock member for upsert
1515 lockMemberById(conn, req, res, req.current_member_id, function() {
1517 // delete supporter
1518 if (params.delete) {
1520 // check issue state
1521 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1523 // delete supporter
1524 var query = new selector.SQLDelete('supporter');
1525 query.addWhere(['initiative_id = ?', initiative_id]);
1526 query.addWhere(['member_id = ?', req.current_member_id]);
1527 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1529 });
1531 // upsert supporter
1532 } else {
1534 // check and lock privilege
1535 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1537 // check issue state
1538 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1540 // check if given draft is the current one
1541 var query = new selector.Selector('current_draft');
1542 query.addField('NULL');
1543 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1544 query.addWhere(['current_draft.id = ?', draft_id]);
1546 db.query(conn, req, res, query, function(result) {
1547 if (result.rows.length != 1) {
1548 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1549 return;
1552 // upsert supporter
1553 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1554 query.addValues({
1555 initiative_id: initiative_id,
1556 member_id: req.current_member_id,
1557 draft_id: draft_id
1558 });
1560 db.query(conn, req, res, query, function(result) {
1561 respond('json', conn, req, res, 'ok');
1562 });
1564 });
1565 });
1566 });
1567 };
1568 });
1569 });
1570 },
1572 '/draft': function (conn, req, res, params) {
1573 requireAccessLevel(conn, req, res, 'member', function() {
1574 var area_id = parseInt(params.area_id);
1575 var policy_id = parseInt(params.policy_id);
1576 var issue_id = parseInt(params.issue_id);
1577 var initiative_id = parseInt(params.initiative_id);
1578 var initiative_name = params.initiative_name;
1579 var initiative_discussion_url = params.initiative_discussion_url;
1580 var formatting_engine = params.formatting_engine;
1581 var content = params.content;
1583 if (!initiative_discussion_url) initiative_discussion_url = null;
1585 // check parameters
1586 if (!formatting_engine) {
1587 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1588 return;
1589 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1590 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1591 return;
1592 };
1594 if (!content) {
1595 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1596 return;
1597 };
1599 lockMemberById(conn, req, res, req.current_member_id, function() {
1601 // new draft in new initiative in new issue
1602 if (area_id && !issue_id && !initiative_id) {
1604 // check parameters for new issue
1605 if (!policy_id) {
1606 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1607 return;
1610 if (!initiative_name) {
1611 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1612 return;
1615 requireAreaPrivilege(conn, req, res, area_id, function() {
1617 // check if policy is allowed in this area and if area and policy are active
1618 var query = new selector.Selector();
1619 query.from('allowed_policy');
1620 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1621 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1622 query.addField('NULL');
1623 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1624 db.query(conn, req, res, query, function (result, conn) {
1625 if (result.rows.length != 1) {
1626 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.');
1627 return;
1628 };
1630 // check contingent
1631 requireContingentLeft(conn, req, res, true, function() {
1633 // insert new issue
1634 var query = new selector.SQLInsert('issue');
1635 query.addValues({
1636 area_id: area_id,
1637 policy_id: policy_id
1638 });
1639 query.addReturning('id');
1640 db.query(conn, req, res, query, function(result) {
1641 var issue_id = result.rows[0].id;
1643 // insert new initiative
1644 var query = new selector.SQLInsert('initiative');
1645 query.addValues({
1646 issue_id: issue_id,
1647 name: initiative_name,
1648 discussion_url: initiative_discussion_url
1649 });
1650 query.addReturning('id');
1651 db.query(conn, req, res, query, function(result) {
1652 var initiative_id = result.rows[0].id;
1654 // insert initiator
1655 var query = new selector.SQLInsert('initiator');
1656 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1657 db.query(conn, req, res, query, function(result) {
1659 // insert new draft
1660 var query = new selector.SQLInsert('draft');
1661 query.addValues({
1662 initiative_id: initiative_id,
1663 author_id: req.current_member_id,
1664 formatting_engine: formatting_engine,
1665 content: content
1666 });
1667 query.addReturning('id');
1668 db.query(conn, req, res, query, function (result, conn) {
1669 var draft_id = result.rows[0].id;
1671 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1672 });
1673 });
1674 });
1675 });
1676 });
1677 });
1678 });
1680 // new draft in new initiative in existant issue
1681 } else if (issue_id && !area_id && !initiative_id) {
1683 if (!initiative_name) {
1684 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1685 return;
1688 // check privilege
1689 requireIssuePrivilege(conn, req, res, issue_id, function() {
1691 // check issue state
1692 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1694 // check contingent
1695 requireContingentLeft(conn, req, res, true, function() {
1697 // insert initiative
1698 var query = new selector.SQLInsert('initiative');
1699 query.addValues({
1700 issue_id: issue_id,
1701 name: initiative_name,
1702 discussion_url: initiative_discussion_url
1703 });
1704 query.addReturning('id');
1705 db.query(conn, req, res, query, function(result) {
1706 var initiative_id = result.rows[0].id;
1708 // insert initiator
1709 var query = new selector.SQLInsert('initiator');
1710 query.addValues({
1711 initiative_id: initiative_id,
1712 member_id: req.current_member_id,
1713 accepted: true
1714 });
1715 db.query(conn, req, res, query, function(result) {
1717 // insert draft
1718 var query = new selector.SQLInsert('draft');
1719 query.addValues({
1720 initiative_id: initiative_id,
1721 author_id: req.current_member_id,
1722 formatting_engine: formatting_engine,
1723 content: content
1724 });
1725 query.addReturning('id');
1726 db.query(conn, req, res, query, function (result, conn) {
1728 var draft_id = result.rows[0].id;
1729 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1731 });
1732 });
1733 });
1734 });
1735 });
1736 });
1738 // new draft in existant initiative
1739 } else if (initiative_id && !area_id && !issue_id ) {
1741 // check privilege
1742 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1744 // check issue state
1745 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1748 // get initiator
1749 var query = new selector.Selector();
1750 query.from('initiator');
1751 query.addField('accepted');
1752 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1753 db.query(conn, req, res, query, function (result, conn) {
1755 // if member is not initiator, deny creating new draft
1756 if (result.rows.length != 1) {
1757 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1758 return;
1760 var initiator = result.rows[0];
1761 if (!initiator.accepted) {
1762 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.');
1763 return;
1764 };
1766 // check contingent
1767 requireContingentLeft(conn, req, res, false, function() {
1769 // insert new draft
1770 var query = new selector.SQLInsert('draft');
1771 query.addValues({
1772 initiative_id: initiative_id,
1773 author_id: req.current_member_id,
1774 formatting_engine: formatting_engine,
1775 content: content
1776 });
1777 query.addReturning('id');
1778 db.query(conn, req, res, query, function (result, conn) {
1780 var draft_id = result.rows[0].id;
1781 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1782 });
1783 });
1784 });
1785 });
1786 });
1788 // none of them (invalid request)
1789 } else {
1790 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1791 };
1793 });
1794 });
1795 },
1797 '/suggestion': function (conn, req, res, params) {
1798 requireAccessLevel(conn, req, res, 'member', function() {
1799 // TODO
1800 });
1801 },
1803 '/opinion': function (conn, req, res, params) {
1804 requireAccessLevel(conn, req, res, 'member', function() {
1805 // TODO
1806 });
1807 },
1809 '/delegation': function (conn, req, res, params) {
1810 requireAccessLevel(conn, req, res, 'member', function() {
1811 var unit_id = parseInt(params.unit_id);
1812 var area_id = parseInt(params.area_id);
1813 var issue_id = parseInt(params.issue_id);
1814 var trustee_id;
1816 if (params.trustee_id == '') {
1817 trustee_id = null;
1818 } else {
1819 trustee_id = parseInt(params.trustee_id);
1822 lockMemberById(conn, req, res, req.current_member_id, function() {
1824 if (params.delete) {
1825 var query = new selector.SQLDelete('delegation')
1826 if (unit_id && !area_id && !issue_id) {
1827 query.addWhere(['unit_id = ?', unit_id]);
1828 } else if (!unit_id && area_id && !issue_id) {
1829 query.addWhere(['area_id = ?', area_id]);
1830 } else if (!unit_id && !area_id && issue_id) {
1831 query.addWhere(['issue_id = ?', issue_id]);
1832 } else {
1833 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1834 return;
1836 query.addWhere(['truster_id = ?', req.current_member_id]);
1837 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1838 } else {
1839 var query = new selector.Upserter('delegation', ['truster_id']);
1840 query.addValues({
1841 truster_id: req.current_member_id,
1842 trustee_id: trustee_id
1843 });
1844 if (unit_id && !area_id && !issue_id) {
1846 // check privilege
1847 requireUnitPrivilege(conn, req, res, unit_id, function() {
1849 query.addKeys(['unit_id'])
1850 query.addValues({ unit_id: unit_id, scope: 'unit' });
1851 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1852 });
1854 } else if (!unit_id && area_id && !issue_id) {
1856 // check privilege
1857 requireAreaPrivilege(conn, req, res, area_id, function() {
1859 query.addKeys(['area_id'])
1860 query.addValues({ area_id: area_id, scope: 'area' });
1861 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1862 });
1864 } else if (!unit_id && !area_id && issue_id) {
1866 // check privilege
1867 requireIssuePrivilege(conn, req, res, issue_id, function() {
1869 // check issue state
1870 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1872 query.addKeys(['issue_id'])
1873 query.addValues({ issue_id: issue_id, scope: 'issue' });
1874 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1875 });
1876 });
1877 } else {
1878 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1879 return;
1883 });
1885 });
1886 },
1888 };

Impressum / About Us