lfapi

view lfapi/main.js @ 32:be8ca05d0315

Replaced reserved token class by clazz
author bsw
date Sat Feb 25 21:56:55 2012 +0100 (2012-02-25)
parents 16fc71c6ab8c
children 25aba6a34c44
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 clazz = include.clazz;
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[clazz + "_id"]) {
144 ids_hash[object[clazz + "_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[clazz + "_id"]) {
153 ids_hash[object[clazz + "_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(clazz);
166 query.addWhere([clazz + '.id IN (??)', ids]);
167 fields.addObjectFields(query, clazz);
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 (clazz == 'policy') {
182 result['policies'] = tmp;
183 } else {
184 result[clazz + 's'] = tmp;
185 }
186 addRelatedData(conn, req, res, result, includes);
187 });
188 } else {
189 respond('json', conn, req, res, 'ok', result);
190 };
192 };
194 function lockMemberById(conn, req, res, member_id, callback) {
195 var query = new selector.Selector('member');
196 query.addField('NULL');
197 query.addWhere(['member.id = ?', member_id]);
198 query.forUpdate();
199 db.query(conn, req, res, query, callback);
200 };
202 function requireUnitPrivilege(conn, req, res, unit_id, callback) {
203 var query = new selector.Selector('privilege');
204 query.addField('NULL');
205 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
206 query.addWhere(['privilege.unit_id = ?', unit_id ]);
207 query.addWhere('privilege.voting_right');
208 query.forShareOf('privilege');
209 db.query(conn, req, res, query, function(result, conn) {
210 if (result.rows.length != 1) {
211 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for this unit.');
212 return;
213 }
214 callback();
215 });
216 };
218 function requireAreaPrivilege(conn, req, res, area_id, callback) {
219 var query = new selector.Selector('privilege');
220 query.join('area', null, 'area.unit_id = privilege.unit_id');
221 query.addField('NULL');
222 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
223 query.addWhere(['area.id = ?', area_id ]);
224 query.addWhere('privilege.voting_right');
225 query.forShareOf('privilege');
226 db.query(conn, req, res, query, function(result, conn) {
227 if (result.rows.length != 1) {
228 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for areas in this unit.');
229 return;
230 }
231 callback();
232 });
233 };
235 function requireIssuePrivilege(conn, req, res, issue_id, callback) {
236 var query = new selector.Selector('privilege');
237 query.join('area', null, 'area.unit_id = privilege.unit_id');
238 query.join('issue', null, 'issue.area_id = area.id');
239 query.addField('NULL');
240 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
241 query.addWhere(['issue.id = ?', issue_id ]);
242 query.addWhere('privilege.voting_right');
243 query.forShareOf('privilege');
244 db.query(conn, req, res, query, function(result, conn) {
245 if (result.rows.length != 1) {
246 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for issues in this unit.');
247 return;
248 }
249 callback();
250 });
251 };
253 function requireInitiativePrivilege(conn, req, res, initiative_id, callback) {
254 var query = new selector.Selector('privilege');
255 query.join('area', null, 'area.unit_id = privilege.unit_id');
256 query.join('issue', null, 'issue.area_id = area.id');
257 query.join('initiative', null, 'initiative.issue_id = issue.id');
258 query.addField('NULL');
259 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
260 query.addWhere(['initiative.id = ?', initiative_id ]);
261 query.addWhere('privilege.voting_right');
262 query.forShareOf('privilege');
263 db.query(conn, req, res, query, function(result, conn) {
264 if (result.rows.length != 1) {
265 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for initiatives in this unit.');
266 return;
267 }
268 callback();
269 });
270 };
272 function requireIssueState(conn, req, res, issue_id, required_states, callback) {
273 var query = new selector.Selector('issue');
274 query.addField('NULL');
275 query.addWhere(['issue.id = ?', issue_id]);
276 query.addWhere(['issue.state IN (??)', required_states]);
277 query.forUpdateOf('issue');
278 db.query(conn, req, res, query, function(result, conn) {
279 if (result.rows.length != 1) {
280 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
281 return;
282 }
283 callback();
284 });
285 };
287 function requireIssueStateForInitiative(conn, req, res, initiative_id, required_states, callback) {
288 var query = new selector.Selector('issue');
289 query.join('initiative', null, 'initiative.issue_id = issue.id');
290 query.addField('NULL');
291 query.addWhere(['initiative.id = ?', initiative_id]);
292 query.addWhere(['issue.state IN (??)', required_states]);
293 query.forUpdateOf('issue');
294 db.query(conn, req, res, query, function(result, conn) {
295 if (result.rows.length != 1) {
296 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
297 return;
298 }
299 callback();
300 });
301 }
303 function requireContingentLeft(conn, req, res, is_initiative, callback) {
304 var query = new selector.Selector('member_contingent_left');
305 query.addField('NULL');
306 query.addWhere(['member_contingent_left.member_id = ?', req.current_member_id]);
307 query.addWhere('member_contingent_left.text_entries_left >= 1');
308 if (is_initiative) {
309 query.addWhere('member_contingent_left.initiatives_left >= 1');
310 }
311 db.query(conn, req, res, query, function(result, conn) {
312 if (result.rows.length != 1) {
313 respond('json', conn, req, res, 'forbidden', null, 'Contingent empty.');
314 return;
315 }
316 callback();
317 });
318 }
320 // ==========================================================================
321 // GET methods
322 // ==========================================================================
325 exports.get = {
327 // startpage (html) for users
328 // currently used for implementing public alpha test
329 '/': function (conn, req, res, params) {
331 var html = [];
332 html.push('<h2>welcome to lfapi public developer alpha test</h2>');
333 html.push('<p>This service is provided for testing purposes and is <i><b>dedicated to developers interested in creating applications</b></i> based on LiquidFeedback.</p>');
334 html.push('<h2>how to use</h2>');
335 html.push('<p>The programming interface is described in the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/API">LiquidFeedback API specification</a>.</p>')
336 html.push('<p>The current implementation status of lfapi is published at the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/lfapi">LiquidFeedback API server</a> page in our Wiki.</p>');
337 html.push('<p><b><i>Neither the API specification nor the implementation of lfapi is finished yet.</i></b> This public test should enable developers to join the specification process of the programming interface and makes it possible to start creating applications.</p>');
338 html.push('<h2>questions and suggestions</h2>');
339 html.push('<p>Please use our <a href="http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main">public mailing list</a> if you have any questions or suggestions.</p>');
340 html.push('<h2>developer registration</h2>');
341 html.push('<p>To register as developer and receive an account, please submit the following form. You\'ll receive an email with instructions to complete the registration process by verifying your email address.<br />');
342 html.push('<form action="register_test" method="POST">');
343 html.push('<label for="name">Your name:</label> <input type="text" id="name" name="name" /> &nbsp; &nbsp; ');
344 html.push('<label for="email">Email address:</label> <input type="text" id="email" name="email" /> &nbsp; &nbsp; ');
345 html.push('<label for="location">Location:</label> <select name="location" id="location"><option value="earth">Earth</option><option value="moon">Moon</option><option value="mars">Mars</option></select>');
346 html.push('<br />');
347 html.push('<br />');
348 html.push('<div style="border: 2px solid #c00000; background-color: #ffa0a0; padding: 1ex;">');
349 html.push('<b>WARNING:</b> All data you entered above and all data you enter later while using the system and all data you are submitting via the programming interface will be stored in the LiquidFeedback database and published. Every access to the system is subject of tracing and logging for development purposes.<br />Please notice, this is a <b>public alpha test dedicated to developers</b>: serious errors can happen, private data unintentionally published or even <a href="http://en.wikipedia.org/wiki/Grey_goo"> grey goo</a> can appear without further warning. Everything is <b>ON YOUR OWN RISK</b>!');
350 html.push('<br />');
351 html.push('<br />');
352 html.push('<input type="checkbox" name="understood" value="understood" /> I understand the previous warning and I understand that everything is on my own risk.<br />');
353 html.push('</div>');
354 html.push('<br />');
355 html.push('<input type="submit" value="Register account" />');
356 respond('html', null, req, res, 'ok', html.join(''));
357 },
359 // temporary method to implement public alpha test
360 '/register_test_confirm': function (conn, req, res, params) {
361 var secret = params.secret;
363 var query = new selector.Selector('member');
364 query.addField('member.id, member.notify_email_unconfirmed');
365 query.addWhere(['member.notify_email_secret = ?', secret]);
366 db.query(conn, req, res, query, function (result, conn) {
367 var member = result.rows[0];
368 if (member) {
369 var query = new selector.SQLUpdate('member');
370 query.addValues({
371 notify_email: member.notify_email_unconfirmed,
372 notify_email_secret: null,
373 notify_email_unconfirmed: null,
374 active: true,
375 activated: 'now',
376 active: true,
377 last_activity: 'now',
378 locked: false
379 });
380 query.addWhere(['id = ?', member.id]);
381 db.query(conn, req, res, query, function (err, result) {
382 respond('html', conn, req, res, 'ok', 'Account activated: ');
383 });
384 } else {
385 respond('html', conn, req, res, 'forbidden', 'Secret not valid or already used.');
386 }
387 })
388 },
390 '/info': function (conn, req, res, params) {
391 requireAccessLevel(conn, req, res, 'anonymous', function() {
392 var query = new selector.Selector();
393 query.from('"liquid_feedback_version"');
394 query.addField('"liquid_feedback_version".*');
395 db.query(conn, req, res, query, function (result, conn) {
396 var liquid_feedback_version = result.rows[0];
397 var query = new selector.Selector();
398 query.from('"system_setting"');
399 query.addField('"member_ttl"');
400 db.query(conn, req, res, query, function (result, conn) {
401 var member_ttl = null;
402 if (result.rows[0]) {
403 member_ttl = result.rows[0].member_ttl;
404 };
405 respond('json', conn, req, res, 'ok', {
406 core_version: liquid_feedback_version.string,
407 api_version: api_version,
408 current_access_level: req.current_member_id ? 'member' : req.current_access_level,
409 current_member_id: req.current_member_id,
410 member_ttl: member_ttl,
411 settings: config.settings
412 });
413 });
414 });
415 });
416 },
418 '/member_count': function (conn, req, res, params) {
419 requireAccessLevel(conn, req, res, 'anonymous', function() {
420 var query = new selector.Selector();
421 query.from('"member_count"');
422 query.addField('"member_count".*');
423 db.query(conn, req, res, query, function (result, conn) {
424 var member_count = result.rows[0];
425 respond('json', conn, req, res, 'ok', {
426 total_count: member_count.total_count,
427 calculated: member_count.calculated
428 });
429 });
430 });
431 },
433 '/contingent': function (conn, req, res, params) {
434 requireAccessLevel(conn, req, res, 'anonymous', function() {
435 var query = new selector.Selector();
436 query.from('"contingent"');
437 query.addField('"contingent".*');
438 db.query(conn, req, res, query, function (result, conn) {
439 respond('json', conn, req, res, 'ok', { result: result.rows });
440 });
441 });
442 },
444 '/contingent_left': function (conn, req, res, params) {
445 requireAccessLevel(conn, req, res, 'member', function() {
446 var query = new selector.Selector();
447 query.from('"member_contingent_left"');
448 query.addField('"member_contingent_left".text_entries_left');
449 query.addField('"member_contingent_left".initiatives_left');
450 query.addWhere(['member_id = ?', req.current_member_id]);
451 db.query(conn, req, res, query, function (result, conn) {
452 respond('json', conn, req, res, 'ok', { result: result.rows[0] });
453 });
454 });
455 },
457 '/member': function (conn, req, res, params) {
458 requireAccessLevel(conn, req, res, 'pseudonym', function() {
459 var query = new selector.Selector();
460 query.from('"member"');
461 if (req.current_access_level == 'pseudonym' && !req.current_member_id ) {
462 fields.addObjectFields(query, 'member', 'member_pseudonym');
463 } else {
464 fields.addObjectFields(query, 'member');
465 }
466 general_params.addMemberOptions(req, query, params);
467 query.addOrderBy('"member"."id"');
468 general_params.addLimitAndOffset(query, params);
469 db.query(conn, req, res, query, function (result, conn) {
470 respond('json', conn, req, res, 'ok', { result: result.rows });
471 });
472 });
473 },
475 '/member_history': function (conn, req, res, params) {
476 requireAccessLevel(conn, req, res, 'full', function() {
477 var query = new selector.Selector();
478 query.from('"member_history" JOIN "member" ON "member"."id" = "member_history"."member_id"');
479 query.addField('"member_history".*');
480 general_params.addMemberOptions(req, query, params);
481 query.addOrderBy('member_history.id');
482 general_params.addLimitAndOffset(query, params);
483 db.query(conn, req, res, query, function (member_history_result, conn) {
484 var result = { result: member_history_result.rows }
485 includes = [];
486 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
487 addRelatedData(conn, req, res, result, includes);
488 });
489 });
490 },
492 '/member_image': function (conn, req, res, params) {
493 requireAccessLevel(conn, req, res, 'full', function() {
494 var query = new selector.Selector();
495 query.from('"member_image" JOIN "member" ON "member"."id" = "member_image"."member_id"');
496 query.addField('"member_image".*');
497 query.addWhere('member_image.scaled');
498 general_params.addMemberOptions(req, query, params);
499 query.addOrderBy = ['member_image.member_id, member_image.image_type'];
500 db.query(conn, req, res, query, function (result, conn) {
501 respond('json', conn, req, res, 'ok', { result: result.rows });
502 });
503 });
504 },
506 '/contact': function (conn, req, res, params) {
507 requireAccessLevel(conn, req, res, 'pseudonym', function() {
508 var query = new selector.Selector();
509 query.from('contact JOIN member ON member.id = contact.member_id');
510 query.addField('"contact".*');
511 if (req.current_member_id) {
512 // public or own for members
513 query.addWhere(['"contact"."public" OR "contact"."member_id" = ?', req.current_member_id]);
514 } else {
515 // public for everybody
516 query.addWhere('"contact"."public"');
517 }
518 general_params.addMemberOptions(req, query, params);
519 query.addOrderBy('"contact"."id"');
520 general_params.addLimitAndOffset(query, params);
521 db.query(conn, req, res, query, function (result, conn) {
522 respond('json', conn, req, res, 'ok', { result: result.rows });
523 });
524 });
525 },
527 '/privilege': function (conn, req, res, params) {
528 requireAccessLevel(conn, req, res, 'pseudonym', function() {
529 var query = new selector.Selector();
530 query.from('privilege JOIN member ON member.id = privilege.member_id JOIN unit ON unit.id = privilege.unit_id');
531 query.addField('privilege.*');
532 general_params.addUnitOptions(req, query, params);
533 general_params.addMemberOptions(req, query, params);
534 query.addOrderBy('privilege.unit_id, privilege.member_id');
535 general_params.addLimitAndOffset(query, params);
536 db.query(conn, req, res, query, function (privilege_result, conn) {
537 var result = { result: privilege_result.rows }
538 includes = [];
539 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
540 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
541 addRelatedData(conn, req, res, result, includes);
542 });
543 });
544 },
546 '/policy': function (conn, req, res, params) {
547 requireAccessLevel(conn, req, res, 'anonymous', function() {
548 var query = new selector.Selector();
549 query.from('"policy"');
550 query.addField('"policy".*');
551 general_params.addPolicyOptions(req, query, params);
552 query.addOrderBy('"policy"."index"');
553 general_params.addLimitAndOffset(query, params);
554 db.query(conn, req, res, query, function (result, conn) {
555 respond('json', conn, req, res, 'ok', { result: result.rows });
556 });
557 });
558 },
560 '/unit': function (conn, req, res, params) {
561 requireAccessLevel(conn, req, res, 'anonymous', function() {
562 var query = new selector.Selector();
563 query.from('"unit"');
564 fields.addObjectFields(query, 'unit');
565 general_params.addUnitOptions(req, query, params);
566 query.addOrderBy('unit.id');
567 general_params.addLimitAndOffset(query, params);
568 db.query(conn, req, res, query, function (result, conn) {
569 respond('json', conn, req, res, 'ok', { result: result.rows });
570 });
571 });
572 },
574 '/area': function (conn, req, res, params) {
575 requireAccessLevel(conn, req, res, 'anonymous', function() {
576 var query = new selector.Selector();
577 query.from('area JOIN unit ON area.unit_id = unit.id');
578 fields.addObjectFields(query, 'area');
579 general_params.addAreaOptions(req, query, params);
580 query.addOrderBy('area.id');
581 general_params.addLimitAndOffset(query, params);
582 db.query(conn, req, res, query, function (area_result, conn) {
583 var result = { result: area_result.rows }
584 includes = [];
585 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
586 addRelatedData(conn, req, res, result, includes);
587 });
588 });
589 },
591 '/allowed_policy': function (conn, req, res, params) {
592 requireAccessLevel(conn, req, res, 'anonymous', function() {
593 var query = new selector.Selector();
594 query.from('allowed_policy');
595 query.join('area', null, 'area.id = allowed_policy.area_id');
596 query.join('unit', null, 'unit.id = area.unit_id');
597 query.addField('allowed_policy.*');
598 general_params.addAreaOptions(req, query, params);
599 query.addOrderBy('allowed_policy.area_id, allowed_policy.policy_id');
600 general_params.addLimitAndOffset(query, params);
601 db.query(conn, req, res, query, function (allowed_policy_result, conn) {
602 var result = { result: allowed_policy_result.rows }
603 includes = [];
604 if (params.include_policies) includes.push({ class: 'policy', objects: 'result'});
605 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
606 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
607 addRelatedData(conn, req, res, result, includes);
608 });
609 }); },
611 '/membership': function (conn, req, res, params) {
612 requireAccessLevel(conn, req, res, 'pseudonym', function() {
613 var query = new selector.Selector();
614 query.from('membership JOIN member ON membership.member_id = member.id JOIN area ON area.id = membership.area_id JOIN unit ON unit.id = area.unit_id');
615 query.addField('membership.*');
616 general_params.addAreaOptions(req, query, params);
617 general_params.addMemberOptions(req, query, params);
618 query.addOrderBy('membership.area_id, membership.member_id');
619 general_params.addLimitAndOffset(query, params);
620 db.query(conn, req, res, query, function (membership_result, conn) {
621 var result = { result: membership_result.rows }
622 includes = [];
623 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
624 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
625 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
626 addRelatedData(conn, req, res, result, includes);
627 });
628 });
629 },
631 '/issue': function (conn, req, res, params) {
632 requireAccessLevel(conn, req, res, 'anonymous', function() {
633 var query = new selector.Selector()
634 query.from('issue JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
635 fields.addObjectFields(query, 'issue');
636 general_params.addIssueOptions(req, query, params);
637 query.addOrderBy('issue.id');
638 general_params.addLimitAndOffset(query, params);
639 db.query(conn, req, res, query, function (issue_result, conn) {
640 var result = { result: issue_result.rows }
641 includes = [];
642 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
643 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
644 if (params.include_policies) includes.push({ class: 'policy', objects: 'result' });
645 addRelatedData(conn, req, res, result, includes);
646 });
647 });
648 },
650 '/population': function (conn, req, res, params) {
651 requireAccessLevel(conn, req, res, 'pseudonym', function() {
652 var query = new selector.Selector();
653 if (params.delegating == '1') {
654 query.from('delegating_population_snapshot', 'population');
655 if (params.delegate_member_id) {
656 query.addWhere(['population.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
657 }
658 if (params.direct_delegate_member_id) {
659 query.addWhere(['population.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
660 }
661 } else {
662 query.from('direct_population_snapshot', 'population');
663 }
664 switch (params.snapshot) {
665 case 'latest':
666 query.addWhere('population.event = issue.latest_snapshot_event');
667 break;
669 case 'end_of_admission':
670 case 'half_freeze':
671 case 'full_freeze':
672 query.addWhere(['population.event = ?', params.snapshot]);
673 break;
675 default:
676 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
677 return;
679 };
680 query.addField('population.*');
681 query.join('member', null, 'member.id = population.member_id');
682 query.join('issue', null, 'population.issue_id = issue.id');
683 query.join('policy', null, 'policy.id = issue.policy_id');
684 query.join('area', null, 'area.id = issue.area_id');
685 query.join('unit', null, 'area.unit_id = unit.id');
686 general_params.addMemberOptions(req, query, params);
687 general_params.addIssueOptions(req, query, params);
688 query.addOrderBy('population.issue_id, population.member_id');
689 general_params.addLimitAndOffset(query, params);
690 db.query(conn, req, res, query, function (population_result, conn) {
691 var result = { result: population_result.rows }
692 includes = [];
693 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
694 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
695 if (params.include_areas) includes.push({ class: 'area', objects: 'areas'});
696 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
697 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
698 addRelatedData(conn, req, res, result, includes);
699 });
700 });
701 },
703 '/interest': function (conn, req, res, params) {
704 requireAccessLevel(conn, req, res, 'pseudonym', function() {
705 var query = new selector.Selector();
706 if (params.snapshot) {
707 if (params.delegating == '1') {
708 query.from('delegating_interest_snapshot', 'interest');
709 if (params.delegate_member_id) {
710 query.addWhere(['interest.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
711 }
712 if (params.direct_delegate_member_id) {
713 query.addWhere(['interest.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
714 }
715 } else {
716 query.from('direct_interest_snapshot', 'interest');
717 }
718 switch (params.snapshot) {
719 case 'latest':
720 query.addWhere('interest.event = issue.latest_snapshot_event');
721 break;
723 case 'end_of_admission':
724 case 'half_freeze':
725 case 'full_freeze':
726 query.addWhere(['interest.event = ?', params.snapshot]);
727 break;
729 default:
730 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
731 return;
733 };
734 } else {
735 if (! req.current_member_id) {
736 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
737 return;
738 };
739 query.from('interest');
740 query.addWhere(['interest.member_id = ?', req.current_member_id]);
741 }
742 query.addField('interest.*');
743 query.join('member', null, 'member.id = interest.member_id');
744 query.join('issue', null, 'interest.issue_id = issue.id');
745 query.join('policy', null, 'policy.id = issue.policy_id');
746 query.join('area', null, 'area.id = issue.area_id');
747 query.join('unit', null, 'area.unit_id = unit.id');
748 general_params.addMemberOptions(req, query, params);
749 general_params.addIssueOptions(req, query, params);
750 query.addOrderBy('interest.issue_id, interest.member_id');
751 general_params.addLimitAndOffset(query, params);
752 db.query(conn, req, res, query, function (interest_result, conn) {
753 var result = { result: interest_result.rows }
754 includes = [];
755 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
756 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
757 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
758 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
759 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
760 addRelatedData(conn, req, res, result, includes);
761 });
762 });
763 },
765 '/issue_comment': function (conn, req, res, params) {
766 requireAccessLevel(conn, req, res, 'pseudonym', function() {
767 var query = new selector.Selector();
768 query.from('issue_comment JOIN member ON member.id = issue_comment.member_id JOIN issue on issue_comment.issue_id = issue.id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
769 query.addField('issue_comment.*');
770 general_params.addMemberOptions(req, query, params);
771 general_params.addIssueOptions(req, query, params);
772 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
773 general_params.addLimitAndOffset(query, params);
774 db.query(conn, req, res, query, function (issue_comment_result, conn) {
775 var result = { result: issue_comment_result.rows }
776 includes = [];
777 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
778 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
779 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
780 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
781 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
782 addRelatedData(conn, req, res, result, includes);
783 });
784 });
785 },
787 '/initiative': function (conn, req, res, params) {
788 requireAccessLevel(conn, req, res, 'anonymous', function() {
789 var query = new selector.Selector();
790 query.from('initiative JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
791 fields.addObjectFields(query, 'initiative');
792 query.addOrderBy('initiative.id');
793 general_params.addInitiativeOptions(req, query, params);
794 general_params.addLimitAndOffset(query, params);
795 db.query(conn, req, res, query, function (initiative_result, conn) {
796 var result = { result: initiative_result.rows }
797 includes = [];
798 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
799 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
800 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
801 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
802 addRelatedData(conn, req, res, result, includes);
803 });
804 });
805 },
807 '/initiator': function (conn, req, res, params) {
808 requireAccessLevel(conn, req, res, 'pseudonym', function() {
809 var fields = ['initiator.initiative_id', 'initiator.member_id'];
810 var query = new selector.Selector();
811 query.from('initiator JOIN member ON member.id = initiator.member_id JOIN initiative ON initiative.id = initiator.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
812 query.addWhere('initiator.accepted');
813 fields.forEach( function(field) {
814 query.addField(field, null, ['grouped']);
815 });
816 general_params.addMemberOptions(req, query, params);
817 general_params.addInitiativeOptions(req, query, params);
818 query.addOrderBy('initiator.initiative_id, initiator.member_id');
819 general_params.addLimitAndOffset(query, params);
820 db.query(conn, req, res, query, function (initiator, conn) {
821 var result = { result: initiator.rows }
822 includes = [];
823 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
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');
841 query.join('initiative', null, 'initiative.id = supporter.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
843 if (params.delegating == '1') {
844 query.join('delegating_interest_snapshot', 'interest', 'interest.issue_id = initiative.issue_id AND interest.delegate_member_ids @> array[supporter.member_id::int] AND interest.event = supporter.event');
845 query.join('member', null, 'member.id = interest.member_id');
846 if (params.delegate_member_id) {
847 query.addWhere(['interest.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
848 }
849 if (params.direct_delegate_member_id) {
850 query.addWhere(['interest.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
851 }
852 } else {
853 query.join('direct_interest_snapshot', 'interest', 'interest.issue_id = initiative.issue_id AND interest.member_id = supporter.member_id AND interest.event = supporter.event');
854 query.join('member', null, 'member.id = supporter.member_id');
855 query.addField('supporter.informed, supporter.satisfied');
856 }
858 query.addField('interest.*')
859 query.addField('supporter.initiative_id');
861 switch (params.snapshot) {
862 case 'latest':
863 query.addWhere('supporter.event = issue.latest_snapshot_event');
864 break;
866 case 'end_of_admission':
867 case 'half_freeze':
868 case 'full_freeze':
869 query.addWhere(['supporter.event = ?', params.snapshot]);
870 break;
872 default:
873 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
874 return;
876 };
878 } else {
879 if (! req.current_member_id) {
880 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
881 return;
882 };
883 query.from('supporter')
884 query.join('member', null, 'member.id = supporter.member_id');
885 query.addField('supporter.*');
886 query.addWhere(['supporter.member_id = ?', req.current_member_id]);
887 }
888 general_params.addMemberOptions(req, query, params);
889 general_params.addInitiativeOptions(req, query, params);
890 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
891 general_params.addLimitAndOffset(query, params);
892 db.query(conn, req, res, query, function (supporter, conn) {
893 var result = { result: supporter.rows }
894 includes = [];
895 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
896 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
897 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
898 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
899 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
900 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
901 addRelatedData(conn, req, res, result, includes);
902 });
903 });
904 },
906 '/battle': function (conn, req, res, params) {
907 requireAccessLevel(conn, req, res, 'anonymous', function() {
908 var query = new selector.Selector();
909 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');
910 query.addField('battle.*');
911 general_params.addInitiativeOptions(req, query, params);
912 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
913 general_params.addLimitAndOffset(query, params);
914 db.query(conn, req, res, query, function (result, conn) {
915 var result = { result: result.rows }
916 includes = [];
917 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
918 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
919 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
920 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
921 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
922 addRelatedData(conn, req, res, result, includes);
923 });
924 });
925 },
927 '/draft': function (conn, req, res, params) {
928 requireAccessLevel(conn, req, res, 'anonymous', function() {
929 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
930 var query = new selector.Selector();
931 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');
932 fields.forEach( function(field) {
933 query.addField(field, null, ['grouped']);
934 });
935 if (req.current_access_level != 'anonymous' || req.current_member_id) {
936 query.addField('draft.author_id');
937 }
938 if (params.draft_id) {
939 query.addWhere('draft.id = ?', params.draft_id);
940 }
941 if (params.current_draft) {
942 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
943 }
944 general_params.addInitiativeOptions(req, query, params);
945 query.addOrderBy('draft.initiative_id, draft.id');
946 general_params.addLimitAndOffset(query, params);
947 db.query(conn, req, res, query, function (result, conn) {
948 var result = { result: result.rows }
949 includes = [];
950 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
951 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
952 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
953 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
954 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
955 addRelatedData(conn, req, res, result, includes);
956 });
957 });
958 },
960 '/suggestion': function (conn, req, res, params) {
961 requireAccessLevel(conn, req, res, 'anonymous', function() {
962 var query = new selector.Selector();
963 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');
964 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
965 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
966 } else {
967 fields.addObjectFields(query, 'suggestion');
968 }
969 general_params.addSuggestionOptions(req, query, params);
970 query.addOrderBy('suggestion.initiative_id, suggestion.id');
971 general_params.addLimitAndOffset(query, params);
972 db.query(conn, req, res, query, function (result, conn) {
973 var result = { result: result.rows }
974 includes = [];
975 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
976 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
977 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
978 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
979 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
980 addRelatedData(conn, req, res, result, includes);
981 });
982 });
983 },
985 '/opinion': function (conn, req, res, params) {
986 requireAccessLevel(conn, req, res, 'pseudonym', function() {
987 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
988 var query = new selector.Selector();
989 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');
990 fields.forEach( function(field) {
991 query.addField(field, null, ['grouped']);
992 });
993 general_params.addMemberOptions(req, query, params);
994 general_params.addSuggestionOptions(req, query, params);
995 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
996 general_params.addLimitAndOffset(query, params);
997 db.query(conn, req, res, query, function (result, conn) {
998 var result = { result: result.rows }
999 includes = [];
1000 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
1001 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
1002 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
1003 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1004 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1005 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1006 addRelatedData(conn, req, res, result, includes);
1007 });
1008 });
1009 },
1011 '/delegation': function (conn, req, res, params) {
1012 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1013 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
1014 var query = new selector.Selector();
1015 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');
1016 fields.forEach( function(field) {
1017 query.addField(field, null, ['grouped']);
1018 });
1019 if (params.direction) {
1020 switch (params.direction) {
1021 case 'in':
1022 query.join('member', null, 'member.id = delegation.trustee_id');
1023 break;
1024 case 'out':
1025 query.join('member', null, 'member.id = delegation.truster_id');
1026 break;
1027 default:
1028 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
1030 } else {
1031 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
1033 general_params.addMemberOptions(req, query, params);
1034 general_params.addIssueOptions(req, query, params);
1035 if (params.scope) {
1036 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
1037 };
1038 query.addOrderBy = ['delegation.id'];
1039 general_params.addLimitAndOffset(query, params);
1040 db.query(conn, req, res, query, function (result, conn) {
1041 respond('json', conn, req, res, 'ok', { result: result.rows });
1042 });
1043 });
1044 },
1046 '/vote': function (conn, req, res, params) {
1047 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1048 var query = new selector.Selector();
1049 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');
1050 query.addField('vote.*');
1051 query.addWhere('issue.closed_at NOTNULL');
1052 general_params.addMemberOptions(req, query, params);
1053 general_params.addInitiativeOptions(req, query, params);
1054 general_params.addLimitAndOffset(query, params);
1055 db.query(conn, req, res, query, function (result, conn) {
1056 respond('json', conn, req, res, 'ok', { result: result.rows });
1057 });
1058 });
1059 },
1061 '/event': function (conn, req, res, params) {
1062 requireAccessLevel(conn, req, res, 'anonymous', function() {
1063 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'];
1064 var query = new selector.Selector();
1065 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');
1066 fields.forEach( function(field) {
1067 query.addField(field, null, ['grouped']);
1068 });
1069 general_params.addMemberOptions(req, query, params);
1070 general_params.addInitiativeOptions(req, query, params);
1071 query.addOrderBy('event.id');
1072 general_params.addLimitAndOffset(query, params);
1073 db.query(conn, req, res, query, function (events, conn) {
1074 var result = { result: events.rows }
1075 includes = [];
1076 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
1077 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
1078 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
1079 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
1080 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
1081 addRelatedData(conn, req, res, result, includes);
1082 });
1083 });
1084 },
1086 // TODO add interfaces for data structure:
1087 // ignored_member requireAccessLevel(conn, req, res, 'member');
1088 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1089 // setting requireAccessLevel(conn, req, res, 'member');
1091 };
1093 // ==========================================================================
1094 // POST methods
1095 // ==========================================================================
1099 exports.post = {
1101 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1102 respond('json', conn, req, res, 'ok', { result: params });
1103 }); },
1105 '/register_test': function (conn, req, res, params) {
1106 var understood = params.understood;
1107 var member_login = randomString(16);
1108 var member_name = params.name;
1109 var member_password = randomString(16);
1110 var member_notify_email = params.email;
1111 var member_notify_email_secret = randomString(24);
1112 var api_key_member = randomString(24);
1113 var api_key_full = randomString(24);
1114 var api_key_pseudonym = randomString(24);
1115 var api_key_anonymous = randomString(24);
1117 if (understood != 'understood') {
1118 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1119 return;
1122 // add member
1123 var query = new selector.SQLInsert('member');
1124 query.addValues({
1125 login: member_login,
1126 password: member_password, // TODO hashing of password
1127 notify_email_unconfirmed: member_notify_email,
1128 notify_email_secret: member_notify_email_secret,
1129 name: member_name
1130 });
1131 query.addReturning('id');
1132 db.query(conn, req, res, query, function (result, conn) {
1133 var member_id = result.rows[0].id;
1135 // add privilege for root unit
1136 var query = new selector.SQLInsert('privilege');
1137 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1138 db.query(conn, req, res, query, function (result, conn) {
1140 var location = params.location;
1141 var unit_id;
1142 switch(location) {
1143 case 'earth':
1144 unit_id = 3;
1145 break;
1146 case 'moon':
1147 unit_id = 4;
1148 break;
1149 case 'mars':
1150 unit_id = 5;
1151 break;
1154 // add privilege for selected planet
1155 var query = new selector.SQLInsert('privilege');
1156 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1157 db.query(conn, req, res, query, function (result, conn) {
1159 // add application key
1160 var query = new selector.SQLInsert('member_application');
1161 query.addValues({
1162 member_id: member_id,
1163 name: 'member',
1164 comment: 'access_level member',
1165 access_level: 'member',
1166 key: api_key_member
1167 });
1168 query.addReturning('id');
1170 db.query(conn, req, res, query, function (result, conn) {
1172 nodemailer.sendmail = '/usr/sbin/sendmail';
1174 // send email to user
1175 nodemailer.send_mail({
1176 sender: config.mail.from,
1177 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1178 to: member_notify_email,
1179 body: "\
1180 Hello " + member_name + ",\n\
1181 \n\
1182 thank you for registering at the public alpha test of the LiquidFeedback\n\
1183 application programming interface. To complete the registration process,\n\
1184 you need to confirm your email address by opening the following URL:\n\
1185 \n\
1186 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1187 \n\
1188 \n\
1189 After you've confirmed your email address, your account will be automatically\n\
1190 activated.\n\
1191 \n\
1192 Your account name is: " + member_name + "\n\
1193 \n\
1194 \n\
1195 You will need the following login and password to register and unregister\n\
1196 applications for your account later. This function is currently not\n\
1197 implemented, but please keep the credentials for future use.\n\
1198 \n\
1199 Account ID: " + member_id + "\n\
1200 Login: " + member_login + "\n\
1201 Password: " + member_password + "\n\
1202 \n\
1203 \n\
1204 To make you able to actually access the API interface, we added the following\n\
1205 application key with full member access privileges to your account:\n\
1206 \n\
1207 API Key: " + api_key_member + "\n\
1208 \n\
1209 \n\
1210 The base address of the public test is: " + config.public_url_path + "\n\
1211 \n\
1212 The programming interface is described in the LiquidFeedback API\n\
1213 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1214 \n\
1215 The current implementation status of lfapi is published at the LiquidFeedback\n\
1216 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1217 \n\
1218 If you have any questions or suggestions, please use our public mailing list\n\
1219 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1220 \n\
1221 For issues regarding your test account, contact us via email at\n\
1222 lqfb-maintainers@public-software-group.org\n\
1223 \n\
1224 \n\
1225 Sincerely,\n\
1226 \n\
1227 Your LiquidFeedback maintainers",
1228 },
1229 function(err, result){
1230 if(err){ console.log(err); }
1231 });
1233 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1234 });
1235 });
1236 });
1237 });
1238 },
1240 /*
1241 '/register': function (conn, req, res, params) {
1242 var invite_key = params.invite_key;
1243 var login = params.login;
1244 var password = params.password;
1245 var name = params.name;
1246 var notify_email = params.notify_email;
1247 if (!invite_key) {
1248 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1249 return;
1250 };
1251 if (!login) {
1252 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1253 return;
1254 };
1255 if (!password) {
1256 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1257 return;
1258 };
1259 if (!name) {
1260 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1261 return;
1262 };
1263 if (!notify_email) {
1264 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1265 return;
1266 };
1267 // check if akey is valid and get member_id for akey
1268 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) {
1269 if (result.rows.length != 1) {
1270 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1271 return;
1272 };
1273 var member_id = result.rows[0].id;
1274 // check if name is available
1275 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1276 if (result.rows.length > 0) {
1277 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1278 return;
1279 };
1280 // check if login is available
1281 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1282 if (result.rows.length > 0) {
1283 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1284 return;
1285 };
1286 var query = { update: 'member', set: { activation: 'now', active: true, } };
1288 });
1289 });
1290 });
1291 },
1292 */
1294 '/session': function (conn, req, res, params) {
1295 var key = params.key;
1296 if (!key) {
1297 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1298 return;
1299 };
1300 var query = new selector.Selector();
1301 query.from('member');
1302 query.join('member_application', null, 'member_application.member_id = member.id');
1303 query.addField('member.id');
1304 query.addWhere(['member.activated NOTNULL AND member_application.key = ?', key]);
1305 if (params.interactive) {
1306 query.forUpdateOf('member');
1308 db.query(conn, req, res, query, function (result, conn) {
1309 if (result.rows.length != 1) {
1310 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1311 return;
1312 };
1313 var member_id = result.rows[0].id;
1314 var session_key = randomString(16);
1315 req.sessions[session_key] = member_id;
1316 var query;
1317 if (params.interactive) {
1318 query = new selector.SQLUpdate('member');
1319 query.addWhere(['member.id = ?', member_id]);
1320 query.addValues({ last_activity: 'now' });
1322 db.query(conn, req, res, query, function (result, conn) {
1323 respond('json', conn, req, res, 'ok', { session_key: session_key });
1324 });
1325 });
1326 },
1328 '/member': function (conn, req, res, params) {
1329 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1330 requireAccessLevel(conn, req, res, 'member', function() {
1331 var query = new selector.SQLUpdate('member');
1332 query.addWhere(['member.id = ?', req.current_member_id]);
1333 fields.forEach( function(field) {
1334 if (typeof(params[field]) != 'undefined') {
1335 query.addValues({ field: params[field] });
1336 } else {
1337 query.addValues({ field: null });
1339 });
1340 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1341 });
1342 },
1344 '/membership': function (conn, req, res, params) {
1345 requireAccessLevel(conn, req, res, 'member', function() {
1347 // check if area_id is set
1348 var area_id = parseInt(params.area_id);
1349 if (!area_id) {
1350 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1351 return;
1354 // delete membership
1355 if (params.delete) {
1356 var query;
1357 query = new selector.SQLDelete('membership');
1358 query.addWhere(['area_id = ?', area_id]);
1359 query.addWhere(['member_id = ?', req.current_member_id]);
1360 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1362 // add membership
1363 } else {
1365 // lock member for upsert
1366 lockMemberById(conn, req, res, req.current_member_id, function() {
1368 // check and lock privilege
1369 requireAreaPrivilege(conn, req, res, area_id, function() {
1371 // upsert membership
1372 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1373 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1374 db.query(conn, req, res, query, function(result) {
1375 respond('json', conn, req, res, 'ok');
1376 });
1377 });
1378 });
1380 });
1381 },
1383 '/interest': function (conn, req, res, params) {
1384 requireAccessLevel(conn, req, res, 'member', function() {
1385 var query;
1387 // check if issue_id is set
1388 var issue_id = parseInt(params.issue_id);
1389 if (!issue_id) {
1390 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1391 return;
1394 // lock member for upsert
1395 lockMemberById(conn, req, res, req.current_member_id, function() {
1397 // delete interest
1398 if (params.delete) {
1400 // check issue state
1401 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1403 // delete interest
1404 query = new selector.SQLDelete('interest');
1405 query.addWhere(['issue_id = ?', issue_id]);
1406 query.addWhere(['member_id = ?', req.current_member_id]);
1407 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1408 });
1410 // add interest
1411 } else {
1413 // check and lock privilege
1414 requireIssuePrivilege(conn, req, res, issue_id, function() {
1416 // check issue state
1417 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1419 // upsert interest
1420 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1421 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1422 db.query(conn, req, res, query, function(result) {
1423 respond('json', conn, req, res, 'ok');
1424 });
1425 });
1426 });
1427 };
1428 });
1429 });
1430 },
1432 '/issue_comment': function (conn, req, res, params) {
1433 requireAccessLevel(conn, req, res, 'member', function() {
1435 var issue_id = parseInt(params.issue_id);
1436 var formatting_engine = params.formatting_engine
1437 var content = params.content;
1439 if (!issue_id) {
1440 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1441 return;
1444 // delete issue comment
1445 if (params.delete) {
1446 var query;
1447 query = new selector.SQLDelete('issue_comment');
1448 query.addWhere(['issue_id = ?', params.issue_id]);
1449 query.addWhere(['member_id = ?', req.current_member_id]);
1450 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1452 // upsert issue comment
1453 } else {
1455 // check if formatting engine is supplied and valid
1456 if (!formatting_engine) {
1457 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1458 return;
1459 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1460 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1461 return;
1462 };
1464 // check if content is supplied
1465 if (!content) {
1466 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1467 return;
1470 // lock member for upsert
1471 lockMemberById(conn, req, res, req.current_member_id, function() {
1473 // check and lock privilege
1474 requireIssuePrivilege(conn, req, res, issue_id, function() {
1476 // upsert issue comment
1477 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1478 query.addValues({
1479 issue_id: issue_id,
1480 member_id: req.current_member_id,
1481 changed: 'now',
1482 formatting_engine: formatting_engine,
1483 content: content
1484 });
1486 db.query(conn, req, res, query, function(result) {
1487 respond('json', conn, req, res, 'ok');
1488 });
1490 });
1491 });
1495 });
1496 },
1498 '/voting_comment': function (conn, req, res, params) {
1499 requireAccessLevel(conn, req, res, 'member', function() {
1501 var issue_id = parseInt(params.issue_id);
1502 var formatting_engine = params.formatting_engine
1503 var content = params.content;
1505 if (!issue_id) {
1506 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1507 return;
1511 // delete voting comment
1512 if (params.delete) {
1513 var query;
1514 query = new selector.SQLDelete('voting_comment');
1515 query.addWhere(['issue_id = ?', params.issue_id]);
1516 query.addWhere(['member_id = ?', req.current_member_id]);
1517 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1519 // upsert voting comment
1520 } else {
1522 // check if formatting engine is supplied and valid
1523 if (!formatting_engine) {
1524 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1525 return;
1526 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1527 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1528 return;
1529 };
1531 // check if content is supplied
1532 if (!content) {
1533 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1534 return;
1537 // lock member for upsert
1538 lockMemberById(conn, req, res, req.current_member_id, function() {
1540 // check and lock privilege
1541 requireIssuePrivilege(conn, req, res, issue_id, function() {
1543 // check issue state
1544 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1546 // upsert voting comment
1547 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1548 query.addValues({
1549 issue_id: issue_id,
1550 member_id: req.current_member_id,
1551 changed: 'now',
1552 formatting_engine: formatting_engine,
1553 content: content
1554 });
1556 db.query(conn, req, res, query, function(result) {
1557 respond('json', conn, req, res, 'ok');
1558 });
1560 });
1561 });
1562 })
1563 };
1564 });
1565 },
1567 '/supporter': function (conn, req, res, params) {
1568 requireAccessLevel(conn, req, res, 'member', function() {
1569 var initiative_id = parseInt(params.initiative_id);
1570 var draft_id = parseInt(params.draft_id);
1572 // check if needed arguments are supplied
1573 if (!initiative_id) {
1574 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1575 return;
1578 if (!draft_id) {
1579 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1580 return;
1583 // lock member for upsert
1584 lockMemberById(conn, req, res, req.current_member_id, function() {
1586 // delete supporter
1587 if (params.delete) {
1589 // check issue state
1590 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1592 // delete supporter
1593 var query = new selector.SQLDelete('supporter');
1594 query.addWhere(['initiative_id = ?', initiative_id]);
1595 query.addWhere(['member_id = ?', req.current_member_id]);
1596 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1598 });
1600 // upsert supporter
1601 } else {
1603 // check and lock privilege
1604 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1606 // check issue state
1607 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1609 // check if given draft is the current one
1610 var query = new selector.Selector('current_draft');
1611 query.addField('NULL');
1612 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1613 query.addWhere(['current_draft.id = ?', draft_id]);
1615 db.query(conn, req, res, query, function(result) {
1616 if (result.rows.length != 1) {
1617 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1618 return;
1621 // upsert supporter
1622 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1623 query.addValues({
1624 initiative_id: initiative_id,
1625 member_id: req.current_member_id,
1626 draft_id: draft_id
1627 });
1629 db.query(conn, req, res, query, function(result) {
1630 respond('json', conn, req, res, 'ok');
1631 });
1633 });
1634 });
1635 });
1636 };
1637 });
1638 });
1639 },
1641 '/draft': function (conn, req, res, params) {
1642 requireAccessLevel(conn, req, res, 'member', function() {
1643 var area_id = parseInt(params.area_id);
1644 var policy_id = parseInt(params.policy_id);
1645 var issue_id = parseInt(params.issue_id);
1646 var initiative_id = parseInt(params.initiative_id);
1647 var initiative_name = params.initiative_name;
1648 var initiative_discussion_url = params.initiative_discussion_url;
1649 var formatting_engine = params.formatting_engine;
1650 var content = params.content;
1652 if (!initiative_discussion_url) initiative_discussion_url = null;
1654 // check parameters
1655 if (!formatting_engine) {
1656 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1657 return;
1658 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1659 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1660 return;
1661 };
1663 if (!content) {
1664 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1665 return;
1666 };
1668 lockMemberById(conn, req, res, req.current_member_id, function() {
1670 // new draft in new initiative in new issue
1671 if (area_id && !issue_id && !initiative_id) {
1673 // check parameters for new issue
1674 if (!policy_id) {
1675 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1676 return;
1679 if (!initiative_name) {
1680 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1681 return;
1684 requireAreaPrivilege(conn, req, res, area_id, function() {
1686 // check if policy is allowed in this area and if area and policy are active
1687 var query = new selector.Selector();
1688 query.from('allowed_policy');
1689 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1690 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1691 query.addField('NULL');
1692 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1693 db.query(conn, req, res, query, function (result, conn) {
1694 if (result.rows.length != 1) {
1695 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.');
1696 return;
1697 };
1699 // check contingent
1700 requireContingentLeft(conn, req, res, true, function() {
1702 // insert new issue
1703 var query = new selector.SQLInsert('issue');
1704 query.addValues({
1705 area_id: area_id,
1706 policy_id: policy_id
1707 });
1708 query.addReturning('id');
1709 db.query(conn, req, res, query, function(result) {
1710 var issue_id = result.rows[0].id;
1712 // insert new initiative
1713 var query = new selector.SQLInsert('initiative');
1714 query.addValues({
1715 issue_id: issue_id,
1716 name: initiative_name,
1717 discussion_url: initiative_discussion_url
1718 });
1719 query.addReturning('id');
1720 db.query(conn, req, res, query, function(result) {
1721 var initiative_id = result.rows[0].id;
1723 // insert initiator
1724 var query = new selector.SQLInsert('initiator');
1725 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1726 db.query(conn, req, res, query, function(result) {
1728 // insert new draft
1729 var query = new selector.SQLInsert('draft');
1730 query.addValues({
1731 initiative_id: initiative_id,
1732 author_id: req.current_member_id,
1733 formatting_engine: formatting_engine,
1734 content: content
1735 });
1736 query.addReturning('id');
1737 db.query(conn, req, res, query, function (result, conn) {
1738 var draft_id = result.rows[0].id;
1740 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1741 });
1742 });
1743 });
1744 });
1745 });
1746 });
1747 });
1749 // new draft in new initiative in existant issue
1750 } else if (issue_id && !area_id && !initiative_id) {
1752 if (!initiative_name) {
1753 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1754 return;
1757 // check privilege
1758 requireIssuePrivilege(conn, req, res, issue_id, function() {
1760 // check issue state
1761 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1763 // check contingent
1764 requireContingentLeft(conn, req, res, true, function() {
1766 // insert initiative
1767 var query = new selector.SQLInsert('initiative');
1768 query.addValues({
1769 issue_id: issue_id,
1770 name: initiative_name,
1771 discussion_url: initiative_discussion_url
1772 });
1773 query.addReturning('id');
1774 db.query(conn, req, res, query, function(result) {
1775 var initiative_id = result.rows[0].id;
1777 // insert initiator
1778 var query = new selector.SQLInsert('initiator');
1779 query.addValues({
1780 initiative_id: initiative_id,
1781 member_id: req.current_member_id,
1782 accepted: true
1783 });
1784 db.query(conn, req, res, query, function(result) {
1786 // insert draft
1787 var query = new selector.SQLInsert('draft');
1788 query.addValues({
1789 initiative_id: initiative_id,
1790 author_id: req.current_member_id,
1791 formatting_engine: formatting_engine,
1792 content: content
1793 });
1794 query.addReturning('id');
1795 db.query(conn, req, res, query, function (result, conn) {
1797 var draft_id = result.rows[0].id;
1798 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1800 });
1801 });
1802 });
1803 });
1804 });
1805 });
1807 // new draft in existant initiative
1808 } else if (initiative_id && !area_id && !issue_id ) {
1810 // check privilege
1811 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1813 // check issue state
1814 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1817 // get initiator
1818 var query = new selector.Selector();
1819 query.from('initiator');
1820 query.addField('accepted');
1821 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1822 db.query(conn, req, res, query, function (result, conn) {
1824 // if member is not initiator, deny creating new draft
1825 if (result.rows.length != 1) {
1826 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1827 return;
1829 var initiator = result.rows[0];
1830 if (!initiator.accepted) {
1831 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.');
1832 return;
1833 };
1835 // check contingent
1836 requireContingentLeft(conn, req, res, false, function() {
1838 // insert new draft
1839 var query = new selector.SQLInsert('draft');
1840 query.addValues({
1841 initiative_id: initiative_id,
1842 author_id: req.current_member_id,
1843 formatting_engine: formatting_engine,
1844 content: content
1845 });
1846 query.addReturning('id');
1847 db.query(conn, req, res, query, function (result, conn) {
1849 var draft_id = result.rows[0].id;
1850 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1851 });
1852 });
1853 });
1854 });
1855 });
1857 // none of them (invalid request)
1858 } else {
1859 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1860 };
1862 });
1863 });
1864 },
1866 '/suggestion': function (conn, req, res, params) {
1867 requireAccessLevel(conn, req, res, 'member', function() {
1868 // TODO
1869 });
1870 },
1872 '/opinion': function (conn, req, res, params) {
1873 requireAccessLevel(conn, req, res, 'member', function() {
1874 // TODO
1875 });
1876 },
1878 '/delegation': function (conn, req, res, params) {
1879 requireAccessLevel(conn, req, res, 'member', function() {
1880 var unit_id = parseInt(params.unit_id);
1881 var area_id = parseInt(params.area_id);
1882 var issue_id = parseInt(params.issue_id);
1883 var trustee_id;
1885 if (params.trustee_id == '') {
1886 trustee_id = null;
1887 } else {
1888 trustee_id = parseInt(params.trustee_id);
1891 lockMemberById(conn, req, res, req.current_member_id, function() {
1893 if (params.delete) {
1894 var query = new selector.SQLDelete('delegation')
1895 if (unit_id && !area_id && !issue_id) {
1896 query.addWhere(['unit_id = ?', unit_id]);
1897 } else if (!unit_id && area_id && !issue_id) {
1898 query.addWhere(['area_id = ?', area_id]);
1899 } else if (!unit_id && !area_id && issue_id) {
1900 query.addWhere(['issue_id = ?', issue_id]);
1901 } else {
1902 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1903 return;
1905 query.addWhere(['truster_id = ?', req.current_member_id]);
1906 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1907 } else {
1908 var query = new selector.Upserter('delegation', ['truster_id']);
1909 query.addValues({
1910 truster_id: req.current_member_id,
1911 trustee_id: trustee_id
1912 });
1913 if (unit_id && !area_id && !issue_id) {
1915 // check privilege
1916 requireUnitPrivilege(conn, req, res, unit_id, function() {
1918 query.addKeys(['unit_id'])
1919 query.addValues({ unit_id: unit_id, scope: 'unit' });
1920 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1921 });
1923 } else if (!unit_id && area_id && !issue_id) {
1925 // check privilege
1926 requireAreaPrivilege(conn, req, res, area_id, function() {
1928 query.addKeys(['area_id'])
1929 query.addValues({ area_id: area_id, scope: 'area' });
1930 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1931 });
1933 } else if (!unit_id && !area_id && issue_id) {
1935 // check privilege
1936 requireIssuePrivilege(conn, req, res, issue_id, function() {
1938 // check issue state
1939 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1941 query.addKeys(['issue_id'])
1942 query.addValues({ issue_id: issue_id, scope: 'issue' });
1943 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1944 });
1945 });
1946 } else {
1947 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1948 return;
1952 });
1954 });
1955 },
1957 };

Impressum / About Us