lfapi

view lfapi/main.js @ 28:a37c91ce44d6

Added snapshot support to GET /supporter
author bsw
date Sun Nov 06 20:24:39 2011 +0100 (2011-11-06)
parents 73670695f8c4
children 1956fe9e9d62
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 }
854 query.addField('interest.*')
855 query.addField('supporter.initiative_id');
857 switch (params.snapshot) {
858 case 'latest':
859 query.addWhere('supporter.event = issue.latest_snapshot_event');
860 break;
862 case 'end_of_admission':
863 case 'half_freeze':
864 case 'full_freeze':
865 query.addWhere(['supporter.event = ?', params.snapshot]);
866 break;
868 default:
869 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
870 return;
872 };
874 } else {
875 if (! req.current_member_id) {
876 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
877 return;
878 };
879 query.from('supporter')
880 query.addField('supporter.*');
881 query.addWhere(['supporter.member_id = ?', req.current_member_id]);
882 }
883 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');
884 general_params.addMemberOptions(req, query, params);
885 general_params.addInitiativeOptions(req, query, params);
886 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
887 general_params.addLimitAndOffset(query, params);
888 db.query(conn, req, res, query, function (supporter, conn) {
889 var result = { result: supporter.rows }
890 includes = [];
891 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
892 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
893 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
894 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
895 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
896 addRelatedData(conn, req, res, result, includes);
897 });
898 });
899 },
901 '/battle': function (conn, req, res, params) {
902 requireAccessLevel(conn, req, res, 'anonymous', function() {
903 var query = new selector.Selector();
904 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');
905 query.addField('battle.*');
906 general_params.addInitiativeOptions(req, query, params);
907 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
908 general_params.addLimitAndOffset(query, params);
909 db.query(conn, req, res, query, function (result, conn) {
910 var result = { result: result.rows }
911 includes = [];
912 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
913 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
914 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
915 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
916 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
917 addRelatedData(conn, req, res, result, includes);
918 });
919 });
920 },
922 '/draft': function (conn, req, res, params) {
923 requireAccessLevel(conn, req, res, 'anonymous', function() {
924 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
925 var query = new selector.Selector();
926 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');
927 fields.forEach( function(field) {
928 query.addField(field, null, ['grouped']);
929 });
930 if (req.current_access_level != 'anonymous' || req.current_member_id) {
931 query.addField('draft.author_id');
932 }
933 if (params.draft_id) {
934 query.addWhere('draft.id = ?', params.draft_id);
935 }
936 if (params.current_draft) {
937 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
938 }
939 general_params.addInitiativeOptions(req, query, params);
940 query.addOrderBy('draft.initiative_id, draft.id');
941 general_params.addLimitAndOffset(query, params);
942 db.query(conn, req, res, query, function (result, conn) {
943 var result = { result: result.rows }
944 includes = [];
945 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
946 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
947 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
948 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
949 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
950 addRelatedData(conn, req, res, result, includes);
951 });
952 });
953 },
955 '/suggestion': function (conn, req, res, params) {
956 requireAccessLevel(conn, req, res, 'anonymous', function() {
957 var query = new selector.Selector();
958 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');
959 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
960 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
961 } else {
962 fields.addObjectFields(query, 'suggestion');
963 }
964 general_params.addSuggestionOptions(req, query, params);
965 query.addOrderBy('suggestion.initiative_id, suggestion.id');
966 general_params.addLimitAndOffset(query, params);
967 db.query(conn, req, res, query, function (result, conn) {
968 var result = { result: result.rows }
969 includes = [];
970 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
971 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
972 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
973 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
974 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
975 addRelatedData(conn, req, res, result, includes);
976 });
977 });
978 },
980 '/opinion': function (conn, req, res, params) {
981 requireAccessLevel(conn, req, res, 'pseudonym', function() {
982 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
983 var query = new selector.Selector();
984 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');
985 fields.forEach( function(field) {
986 query.addField(field, null, ['grouped']);
987 });
988 general_params.addMemberOptions(req, query, params);
989 general_params.addSuggestionOptions(req, query, params);
990 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
991 general_params.addLimitAndOffset(query, params);
992 db.query(conn, req, res, query, function (result, conn) {
993 var result = { result: result.rows }
994 includes = [];
995 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
996 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
997 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
998 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
999 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1000 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1001 addRelatedData(conn, req, res, result, includes);
1002 });
1003 });
1004 },
1006 '/delegation': function (conn, req, res, params) {
1007 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1008 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
1009 var query = new selector.Selector();
1010 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');
1011 fields.forEach( function(field) {
1012 query.addField(field, null, ['grouped']);
1013 });
1014 if (params.direction) {
1015 switch (params.direction) {
1016 case 'in':
1017 query.join('member', null, 'member.id = delegation.trustee_id');
1018 break;
1019 case 'out':
1020 query.join('member', null, 'member.id = delegation.truster_id');
1021 break;
1022 default:
1023 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
1025 } else {
1026 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
1028 general_params.addMemberOptions(req, query, params);
1029 general_params.addIssueOptions(req, query, params);
1030 if (params.scope) {
1031 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
1032 };
1033 query.addOrderBy = ['delegation.id'];
1034 general_params.addLimitAndOffset(query, params);
1035 db.query(conn, req, res, query, function (result, conn) {
1036 respond('json', conn, req, res, 'ok', { result: result.rows });
1037 });
1038 });
1039 },
1041 '/vote': function (conn, req, res, params) {
1042 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1043 var query = new selector.Selector();
1044 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');
1045 query.addField('vote.*');
1046 query.addWhere('issue.closed_at NOTNULL');
1047 general_params.addMemberOptions(req, query, params);
1048 general_params.addInitiativeOptions(req, query, params);
1049 general_params.addLimitAndOffset(query, params);
1050 db.query(conn, req, res, query, function (result, conn) {
1051 respond('json', conn, req, res, 'ok', { result: result.rows });
1052 });
1053 });
1054 },
1056 '/event': function (conn, req, res, params) {
1057 requireAccessLevel(conn, req, res, 'anonymous', function() {
1058 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'];
1059 var query = new selector.Selector();
1060 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');
1061 fields.forEach( function(field) {
1062 query.addField(field, null, ['grouped']);
1063 });
1064 general_params.addMemberOptions(req, query, params);
1065 general_params.addInitiativeOptions(req, query, params);
1066 query.addOrderBy('event.id');
1067 general_params.addLimitAndOffset(query, params);
1068 db.query(conn, req, res, query, function (events, conn) {
1069 var result = { result: events.rows }
1070 includes = [];
1071 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
1072 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
1073 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1074 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1075 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1076 addRelatedData(conn, req, res, result, includes);
1077 });
1078 });
1079 },
1081 // TODO add interfaces for data structure:
1082 // ignored_member requireAccessLevel(conn, req, res, 'member');
1083 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1084 // setting requireAccessLevel(conn, req, res, 'member');
1086 };
1088 // ==========================================================================
1089 // POST methods
1090 // ==========================================================================
1094 exports.post = {
1096 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1097 respond('json', conn, req, res, 'ok', { result: params });
1098 }); },
1100 '/register_test': function (conn, req, res, params) {
1101 var understood = params.understood;
1102 var member_login = randomString(16);
1103 var member_name = params.name;
1104 var member_password = randomString(16);
1105 var member_notify_email = params.email;
1106 var member_notify_email_secret = randomString(24);
1107 var api_key_member = randomString(24);
1108 var api_key_full = randomString(24);
1109 var api_key_pseudonym = randomString(24);
1110 var api_key_anonymous = randomString(24);
1112 if (understood != 'understood') {
1113 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1114 return;
1117 // add member
1118 var query = new selector.SQLInsert('member');
1119 query.addValues({
1120 login: member_login,
1121 password: member_password, // TODO hashing of password
1122 notify_email_unconfirmed: member_notify_email,
1123 notify_email_secret: member_notify_email_secret,
1124 name: member_name
1125 });
1126 query.addReturning('id');
1127 db.query(conn, req, res, query, function (result, conn) {
1128 var member_id = result.rows[0].id;
1130 // add privilege for root unit
1131 var query = new selector.SQLInsert('privilege');
1132 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1133 db.query(conn, req, res, query, function (result, conn) {
1135 var location = params.location;
1136 var unit_id;
1137 switch(location) {
1138 case 'earth':
1139 unit_id = 3;
1140 break;
1141 case 'moon':
1142 unit_id = 4;
1143 break;
1144 case 'mars':
1145 unit_id = 5;
1146 break;
1149 // add privilege for selected planet
1150 var query = new selector.SQLInsert('privilege');
1151 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1152 db.query(conn, req, res, query, function (result, conn) {
1154 // add application key
1155 var query = new selector.SQLInsert('member_application');
1156 query.addValues({
1157 member_id: member_id,
1158 name: 'member',
1159 comment: 'access_level member',
1160 access_level: 'member',
1161 key: api_key_member
1162 });
1163 query.addReturning('id');
1165 db.query(conn, req, res, query, function (result, conn) {
1167 nodemailer.sendmail = '/usr/sbin/sendmail';
1169 // send email to user
1170 nodemailer.send_mail({
1171 sender: config.mail.from,
1172 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1173 to: member_notify_email,
1174 body: "\
1175 Hello " + member_name + ",\n\
1176 \n\
1177 thank you for registering at the public alpha test of the LiquidFeedback\n\
1178 application programming interface. To complete the registration process,\n\
1179 you need to confirm your email address by opening the following URL:\n\
1180 \n\
1181 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1182 \n\
1183 \n\
1184 After you've confirmed your email address, your account will be automatically\n\
1185 activated.\n\
1186 \n\
1187 Your account name is: " + member_name + "\n\
1188 \n\
1189 \n\
1190 You will need the following login and password to register and unregister\n\
1191 applications for your account later. This function is currently not\n\
1192 implemented, but please keep the credentials for future use.\n\
1193 \n\
1194 Account ID: " + member_id + "\n\
1195 Login: " + member_login + "\n\
1196 Password: " + member_password + "\n\
1197 \n\
1198 \n\
1199 To make you able to actually access the API interface, we added the following\n\
1200 application key with full member access privileges to your account:\n\
1201 \n\
1202 API Key: " + api_key_member + "\n\
1203 \n\
1204 \n\
1205 The base address of the public test is: " + config.public_url_path + "\n\
1206 \n\
1207 The programming interface is described in the LiquidFeedback API\n\
1208 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1209 \n\
1210 The current implementation status of lfapi is published at the LiquidFeedback\n\
1211 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1212 \n\
1213 If you have any questions or suggestions, please use our public mailing list\n\
1214 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1215 \n\
1216 For issues regarding your test account, contact us via email at\n\
1217 lqfb-maintainers@public-software-group.org\n\
1218 \n\
1219 \n\
1220 Sincerely,\n\
1221 \n\
1222 Your LiquidFeedback maintainers",
1223 },
1224 function(err, result){
1225 if(err){ console.log(err); }
1226 });
1228 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1229 });
1230 });
1231 });
1232 });
1233 },
1235 /*
1236 '/register': function (conn, req, res, params) {
1237 var invite_key = params.invite_key;
1238 var login = params.login;
1239 var password = params.password;
1240 var name = params.name;
1241 var notify_email = params.notify_email;
1242 if (!invite_key) {
1243 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1244 return;
1245 };
1246 if (!login) {
1247 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1248 return;
1249 };
1250 if (!password) {
1251 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1252 return;
1253 };
1254 if (!name) {
1255 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1256 return;
1257 };
1258 if (!notify_email) {
1259 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1260 return;
1261 };
1262 // check if akey is valid and get member_id for akey
1263 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) {
1264 if (result.rows.length != 1) {
1265 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1266 return;
1267 };
1268 var member_id = result.rows[0].id;
1269 // check if name is available
1270 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1271 if (result.rows.length > 0) {
1272 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1273 return;
1274 };
1275 // check if login is available
1276 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1277 if (result.rows.length > 0) {
1278 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1279 return;
1280 };
1281 var query = { update: 'member', set: { activation: 'now', active: true, } };
1283 });
1284 });
1285 });
1286 },
1287 */
1289 '/session': function (conn, req, res, params) {
1290 var key = params.key;
1291 if (!key) {
1292 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1293 return;
1294 };
1295 var query = new selector.Selector();
1296 query.from('member');
1297 query.join('member_application', null, 'member_application.member_id = member.id');
1298 query.addField('member.id');
1299 query.addWhere(['member.activated NOTNULL AND member_application.key = ?', key]);
1300 if (params.interactive) {
1301 query.forUpdateOf('member');
1303 db.query(conn, req, res, query, function (result, conn) {
1304 if (result.rows.length != 1) {
1305 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1306 return;
1307 };
1308 var member_id = result.rows[0].id;
1309 var session_key = randomString(16);
1310 req.sessions[session_key] = member_id;
1311 var query;
1312 if (params.interactive) {
1313 query = new selector.SQLUpdate('member');
1314 query.addWhere(['member.id = ?', member_id]);
1315 query.addValues({ last_activity: 'now' });
1317 db.query(conn, req, res, query, function (result, conn) {
1318 respond('json', conn, req, res, 'ok', { session_key: session_key });
1319 });
1320 });
1321 },
1323 '/member': function (conn, req, res, params) {
1324 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1325 requireAccessLevel(conn, req, res, 'member', function() {
1326 var query = new selector.SQLUpdate('member');
1327 query.addWhere(['member.id = ?', req.current_member_id]);
1328 fields.forEach( function(field) {
1329 if (typeof(params[field]) != 'undefined') {
1330 query.addValues({ field: params[field] });
1331 } else {
1332 query.addValues({ field: null });
1334 });
1335 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1336 });
1337 },
1339 '/membership': function (conn, req, res, params) {
1340 requireAccessLevel(conn, req, res, 'member', function() {
1342 // check if area_id is set
1343 var area_id = parseInt(params.area_id);
1344 if (!area_id) {
1345 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1346 return;
1349 // delete membership
1350 if (params.delete) {
1351 var query;
1352 query = new selector.SQLDelete('membership');
1353 query.addWhere(['area_id = ?', area_id]);
1354 query.addWhere(['member_id = ?', req.current_member_id]);
1355 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1357 // add membership
1358 } else {
1360 // lock member for upsert
1361 lockMemberById(conn, req, res, req.current_member_id, function() {
1363 // check and lock privilege
1364 requireAreaPrivilege(conn, req, res, area_id, function() {
1366 // upsert membership
1367 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1368 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1369 db.query(conn, req, res, query, function(result) {
1370 respond('json', conn, req, res, 'ok');
1371 });
1372 });
1373 });
1375 });
1376 },
1378 '/interest': function (conn, req, res, params) {
1379 requireAccessLevel(conn, req, res, 'member', function() {
1380 var query;
1382 // check if issue_id is set
1383 var issue_id = parseInt(params.issue_id);
1384 if (!issue_id) {
1385 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1386 return;
1389 // lock member for upsert
1390 lockMemberById(conn, req, res, req.current_member_id, function() {
1392 // delete interest
1393 if (params.delete) {
1395 // check issue state
1396 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1398 // delete interest
1399 query = new selector.SQLDelete('interest');
1400 query.addWhere(['issue_id = ?', issue_id]);
1401 query.addWhere(['member_id = ?', req.current_member_id]);
1402 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1403 });
1405 // add interest
1406 } else {
1408 // check and lock privilege
1409 requireIssuePrivilege(conn, req, res, issue_id, function() {
1411 // check issue state
1412 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1414 // upsert interest
1415 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1416 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1417 db.query(conn, req, res, query, function(result) {
1418 respond('json', conn, req, res, 'ok');
1419 });
1420 });
1421 });
1422 };
1423 });
1424 });
1425 },
1427 '/issue_comment': function (conn, req, res, params) {
1428 requireAccessLevel(conn, req, res, 'member', function() {
1430 var issue_id = parseInt(params.issue_id);
1431 var formatting_engine = params.formatting_engine
1432 var content = params.content;
1434 if (!issue_id) {
1435 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1436 return;
1439 // delete issue comment
1440 if (params.delete) {
1441 var query;
1442 query = new selector.SQLDelete('issue_comment');
1443 query.addWhere(['issue_id = ?', params.issue_id]);
1444 query.addWhere(['member_id = ?', req.current_member_id]);
1445 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1447 // upsert issue comment
1448 } else {
1450 // check if formatting engine is supplied and valid
1451 if (!formatting_engine) {
1452 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1453 return;
1454 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1455 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1456 return;
1457 };
1459 // check if content is supplied
1460 if (!content) {
1461 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1462 return;
1465 // lock member for upsert
1466 lockMemberById(conn, req, res, req.current_member_id, function() {
1468 // check and lock privilege
1469 requireIssuePrivilege(conn, req, res, issue_id, function() {
1471 // upsert issue comment
1472 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1473 query.addValues({
1474 issue_id: issue_id,
1475 member_id: req.current_member_id,
1476 changed: 'now',
1477 formatting_engine: formatting_engine,
1478 content: content
1479 });
1481 db.query(conn, req, res, query, function(result) {
1482 respond('json', conn, req, res, 'ok');
1483 });
1485 });
1486 });
1490 });
1491 },
1493 '/voting_comment': function (conn, req, res, params) {
1494 requireAccessLevel(conn, req, res, 'member', function() {
1496 var issue_id = parseInt(params.issue_id);
1497 var formatting_engine = params.formatting_engine
1498 var content = params.content;
1500 if (!issue_id) {
1501 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1502 return;
1506 // delete voting comment
1507 if (params.delete) {
1508 var query;
1509 query = new selector.SQLDelete('voting_comment');
1510 query.addWhere(['issue_id = ?', params.issue_id]);
1511 query.addWhere(['member_id = ?', req.current_member_id]);
1512 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1514 // upsert voting comment
1515 } else {
1517 // check if formatting engine is supplied and valid
1518 if (!formatting_engine) {
1519 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1520 return;
1521 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1522 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1523 return;
1524 };
1526 // check if content is supplied
1527 if (!content) {
1528 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1529 return;
1532 // lock member for upsert
1533 lockMemberById(conn, req, res, req.current_member_id, function() {
1535 // check and lock privilege
1536 requireIssuePrivilege(conn, req, res, issue_id, function() {
1538 // check issue state
1539 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1541 // upsert voting comment
1542 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1543 query.addValues({
1544 issue_id: issue_id,
1545 member_id: req.current_member_id,
1546 changed: 'now',
1547 formatting_engine: formatting_engine,
1548 content: content
1549 });
1551 db.query(conn, req, res, query, function(result) {
1552 respond('json', conn, req, res, 'ok');
1553 });
1555 });
1556 });
1557 })
1558 };
1559 });
1560 },
1562 '/supporter': function (conn, req, res, params) {
1563 requireAccessLevel(conn, req, res, 'member', function() {
1564 var initiative_id = parseInt(params.initiative_id);
1565 var draft_id = parseInt(params.draft_id);
1567 // check if needed arguments are supplied
1568 if (!initiative_id) {
1569 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1570 return;
1573 if (!draft_id) {
1574 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1575 return;
1578 // lock member for upsert
1579 lockMemberById(conn, req, res, req.current_member_id, function() {
1581 // delete supporter
1582 if (params.delete) {
1584 // check issue state
1585 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1587 // delete supporter
1588 var query = new selector.SQLDelete('supporter');
1589 query.addWhere(['initiative_id = ?', initiative_id]);
1590 query.addWhere(['member_id = ?', req.current_member_id]);
1591 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1593 });
1595 // upsert supporter
1596 } else {
1598 // check and lock privilege
1599 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1601 // check issue state
1602 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1604 // check if given draft is the current one
1605 var query = new selector.Selector('current_draft');
1606 query.addField('NULL');
1607 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1608 query.addWhere(['current_draft.id = ?', draft_id]);
1610 db.query(conn, req, res, query, function(result) {
1611 if (result.rows.length != 1) {
1612 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1613 return;
1616 // upsert supporter
1617 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1618 query.addValues({
1619 initiative_id: initiative_id,
1620 member_id: req.current_member_id,
1621 draft_id: draft_id
1622 });
1624 db.query(conn, req, res, query, function(result) {
1625 respond('json', conn, req, res, 'ok');
1626 });
1628 });
1629 });
1630 });
1631 };
1632 });
1633 });
1634 },
1636 '/draft': function (conn, req, res, params) {
1637 requireAccessLevel(conn, req, res, 'member', function() {
1638 var area_id = parseInt(params.area_id);
1639 var policy_id = parseInt(params.policy_id);
1640 var issue_id = parseInt(params.issue_id);
1641 var initiative_id = parseInt(params.initiative_id);
1642 var initiative_name = params.initiative_name;
1643 var initiative_discussion_url = params.initiative_discussion_url;
1644 var formatting_engine = params.formatting_engine;
1645 var content = params.content;
1647 if (!initiative_discussion_url) initiative_discussion_url = null;
1649 // check parameters
1650 if (!formatting_engine) {
1651 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1652 return;
1653 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1654 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1655 return;
1656 };
1658 if (!content) {
1659 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1660 return;
1661 };
1663 lockMemberById(conn, req, res, req.current_member_id, function() {
1665 // new draft in new initiative in new issue
1666 if (area_id && !issue_id && !initiative_id) {
1668 // check parameters for new issue
1669 if (!policy_id) {
1670 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1671 return;
1674 if (!initiative_name) {
1675 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1676 return;
1679 requireAreaPrivilege(conn, req, res, area_id, function() {
1681 // check if policy is allowed in this area and if area and policy are active
1682 var query = new selector.Selector();
1683 query.from('allowed_policy');
1684 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1685 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1686 query.addField('NULL');
1687 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1688 db.query(conn, req, res, query, function (result, conn) {
1689 if (result.rows.length != 1) {
1690 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.');
1691 return;
1692 };
1694 // check contingent
1695 requireContingentLeft(conn, req, res, true, function() {
1697 // insert new issue
1698 var query = new selector.SQLInsert('issue');
1699 query.addValues({
1700 area_id: area_id,
1701 policy_id: policy_id
1702 });
1703 query.addReturning('id');
1704 db.query(conn, req, res, query, function(result) {
1705 var issue_id = result.rows[0].id;
1707 // insert new initiative
1708 var query = new selector.SQLInsert('initiative');
1709 query.addValues({
1710 issue_id: issue_id,
1711 name: initiative_name,
1712 discussion_url: initiative_discussion_url
1713 });
1714 query.addReturning('id');
1715 db.query(conn, req, res, query, function(result) {
1716 var initiative_id = result.rows[0].id;
1718 // insert initiator
1719 var query = new selector.SQLInsert('initiator');
1720 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1721 db.query(conn, req, res, query, function(result) {
1723 // insert new draft
1724 var query = new selector.SQLInsert('draft');
1725 query.addValues({
1726 initiative_id: initiative_id,
1727 author_id: req.current_member_id,
1728 formatting_engine: formatting_engine,
1729 content: content
1730 });
1731 query.addReturning('id');
1732 db.query(conn, req, res, query, function (result, conn) {
1733 var draft_id = result.rows[0].id;
1735 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1736 });
1737 });
1738 });
1739 });
1740 });
1741 });
1742 });
1744 // new draft in new initiative in existant issue
1745 } else if (issue_id && !area_id && !initiative_id) {
1747 if (!initiative_name) {
1748 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1749 return;
1752 // check privilege
1753 requireIssuePrivilege(conn, req, res, issue_id, function() {
1755 // check issue state
1756 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1758 // check contingent
1759 requireContingentLeft(conn, req, res, true, function() {
1761 // insert initiative
1762 var query = new selector.SQLInsert('initiative');
1763 query.addValues({
1764 issue_id: issue_id,
1765 name: initiative_name,
1766 discussion_url: initiative_discussion_url
1767 });
1768 query.addReturning('id');
1769 db.query(conn, req, res, query, function(result) {
1770 var initiative_id = result.rows[0].id;
1772 // insert initiator
1773 var query = new selector.SQLInsert('initiator');
1774 query.addValues({
1775 initiative_id: initiative_id,
1776 member_id: req.current_member_id,
1777 accepted: true
1778 });
1779 db.query(conn, req, res, query, function(result) {
1781 // insert draft
1782 var query = new selector.SQLInsert('draft');
1783 query.addValues({
1784 initiative_id: initiative_id,
1785 author_id: req.current_member_id,
1786 formatting_engine: formatting_engine,
1787 content: content
1788 });
1789 query.addReturning('id');
1790 db.query(conn, req, res, query, function (result, conn) {
1792 var draft_id = result.rows[0].id;
1793 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1795 });
1796 });
1797 });
1798 });
1799 });
1800 });
1802 // new draft in existant initiative
1803 } else if (initiative_id && !area_id && !issue_id ) {
1805 // check privilege
1806 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1808 // check issue state
1809 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1812 // get initiator
1813 var query = new selector.Selector();
1814 query.from('initiator');
1815 query.addField('accepted');
1816 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1817 db.query(conn, req, res, query, function (result, conn) {
1819 // if member is not initiator, deny creating new draft
1820 if (result.rows.length != 1) {
1821 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1822 return;
1824 var initiator = result.rows[0];
1825 if (!initiator.accepted) {
1826 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.');
1827 return;
1828 };
1830 // check contingent
1831 requireContingentLeft(conn, req, res, false, function() {
1833 // insert new draft
1834 var query = new selector.SQLInsert('draft');
1835 query.addValues({
1836 initiative_id: initiative_id,
1837 author_id: req.current_member_id,
1838 formatting_engine: formatting_engine,
1839 content: content
1840 });
1841 query.addReturning('id');
1842 db.query(conn, req, res, query, function (result, conn) {
1844 var draft_id = result.rows[0].id;
1845 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1846 });
1847 });
1848 });
1849 });
1850 });
1852 // none of them (invalid request)
1853 } else {
1854 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1855 };
1857 });
1858 });
1859 },
1861 '/suggestion': function (conn, req, res, params) {
1862 requireAccessLevel(conn, req, res, 'member', function() {
1863 // TODO
1864 });
1865 },
1867 '/opinion': function (conn, req, res, params) {
1868 requireAccessLevel(conn, req, res, 'member', function() {
1869 // TODO
1870 });
1871 },
1873 '/delegation': function (conn, req, res, params) {
1874 requireAccessLevel(conn, req, res, 'member', function() {
1875 var unit_id = parseInt(params.unit_id);
1876 var area_id = parseInt(params.area_id);
1877 var issue_id = parseInt(params.issue_id);
1878 var trustee_id;
1880 if (params.trustee_id == '') {
1881 trustee_id = null;
1882 } else {
1883 trustee_id = parseInt(params.trustee_id);
1886 lockMemberById(conn, req, res, req.current_member_id, function() {
1888 if (params.delete) {
1889 var query = new selector.SQLDelete('delegation')
1890 if (unit_id && !area_id && !issue_id) {
1891 query.addWhere(['unit_id = ?', unit_id]);
1892 } else if (!unit_id && area_id && !issue_id) {
1893 query.addWhere(['area_id = ?', area_id]);
1894 } else if (!unit_id && !area_id && issue_id) {
1895 query.addWhere(['issue_id = ?', issue_id]);
1896 } else {
1897 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1898 return;
1900 query.addWhere(['truster_id = ?', req.current_member_id]);
1901 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1902 } else {
1903 var query = new selector.Upserter('delegation', ['truster_id']);
1904 query.addValues({
1905 truster_id: req.current_member_id,
1906 trustee_id: trustee_id
1907 });
1908 if (unit_id && !area_id && !issue_id) {
1910 // check privilege
1911 requireUnitPrivilege(conn, req, res, unit_id, function() {
1913 query.addKeys(['unit_id'])
1914 query.addValues({ unit_id: unit_id, scope: 'unit' });
1915 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1916 });
1918 } else if (!unit_id && area_id && !issue_id) {
1920 // check privilege
1921 requireAreaPrivilege(conn, req, res, area_id, function() {
1923 query.addKeys(['area_id'])
1924 query.addValues({ area_id: area_id, scope: 'area' });
1925 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1926 });
1928 } else if (!unit_id && !area_id && issue_id) {
1930 // check privilege
1931 requireIssuePrivilege(conn, req, res, issue_id, function() {
1933 // check issue state
1934 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1936 query.addKeys(['issue_id'])
1937 query.addValues({ issue_id: issue_id, scope: 'issue' });
1938 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1939 });
1940 });
1941 } else {
1942 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1943 return;
1947 });
1949 });
1950 },
1952 };

Impressum / About Us