lfapi

view lfapi/main.js @ 31:16fc71c6ab8c

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

Impressum / About Us