lfapi

view lfapi/main.js @ 29:1956fe9e9d62

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

Impressum / About Us