lfapi

view lfapi/main.js @ 18:da041f00018a

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

Impressum / About Us