lfapi

view lfapi/main.js @ 33:25aba6a34c44

Added missing related objects, fixed smaller bugs,
author bsw
date Sat May 19 13:58:50 2012 +0200 (2012-05-19)
parents be8ca05d0315
children a5a5de8dbac2
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 'Access-Control-Allow-Origin': '*'
103 //'Content-Length': body.length // TODO doesn't work in chrome with JSONP
104 }
105 );
106 res.end(body);
107 } else if (mode == 'html') {
108 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>']
109 body.push(object)
110 body.push('</body></html>')
111 body = body.join('');
112 res.writeHead(
113 http_status,
114 {
115 'Content-Type': 'text/html; charset=UTF-8',
116 'Content-Length': body.length
117 }
118 );
119 res.end(body);
120 }
121 })
122 });
123 };
125 exports.respond = respond;
126 db.error_handler = respond;
128 // add requested related data for requests with include_* parameters
129 function addRelatedData(conn, req, res, result, includes) {
130 if (includes.length > 0) {
131 var include = includes.shift();
132 var clazz = include.clazz;
133 var objects = result[include.objects];
135 var query;
137 if (objects) {
138 var objects_exists = false;
139 var ids_hash = {};
140 if (typeof(objects) == 'array') {
141 if (objects.length > 0) {
142 objects_exists = true;
143 objects.forEach( function(object) {
144 if (object[clazz + "_id"]) {
145 ids_hash[object[clazz + "_id"]] = true;
146 };
147 });
148 }
149 } else {
150 for (var key in objects) {
151 objects_exists = true;
152 var object = objects[key];
153 if (object[clazz + "_id"]) {
154 ids_hash[object[clazz + "_id"]] = true;
155 };
156 };
157 };
159 if (objects_exists) {
160 var ids = [];
161 for (key in ids_hash) {
162 ids.push(key)
163 }
164 if (ids.length > 0) {
165 query = new selector.Selector();
166 query.from(clazz);
167 query.addWhere([clazz + '.id IN (??)', ids]);
168 fields.addObjectFields(query, clazz);
169 }
170 };
171 };
173 db.query(conn, req, res, query, function (result2, conn) {
174 // add result to main result, regarding correct pluralization
175 var tmp = {};
176 if (result2) {
177 result2.rows.forEach( function(row) {
178 tmp[row.id] = row;
179 });
180 };
182 if (clazz == 'policy') {
183 result['policies'] = tmp;
184 } else {
185 result[clazz + 's'] = tmp;
186 }
187 addRelatedData(conn, req, res, result, includes);
188 });
189 } else {
190 respond('json', conn, req, res, 'ok', result);
191 };
193 };
195 function lockMemberById(conn, req, res, member_id, callback) {
196 var query = new selector.Selector('member');
197 query.addField('NULL');
198 query.addWhere(['member.id = ?', member_id]);
199 query.forUpdate();
200 db.query(conn, req, res, query, callback);
201 };
203 function requireUnitPrivilege(conn, req, res, unit_id, callback) {
204 var query = new selector.Selector('privilege');
205 query.addField('NULL');
206 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
207 query.addWhere(['privilege.unit_id = ?', unit_id ]);
208 query.addWhere('privilege.voting_right');
209 query.forShareOf('privilege');
210 db.query(conn, req, res, query, function(result, conn) {
211 if (result.rows.length != 1) {
212 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for this unit.');
213 return;
214 }
215 callback();
216 });
217 };
219 function requireAreaPrivilege(conn, req, res, area_id, callback) {
220 var query = new selector.Selector('privilege');
221 query.join('area', null, 'area.unit_id = privilege.unit_id');
222 query.addField('NULL');
223 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
224 query.addWhere(['area.id = ?', area_id ]);
225 query.addWhere('privilege.voting_right');
226 query.forShareOf('privilege');
227 db.query(conn, req, res, query, function(result, conn) {
228 if (result.rows.length != 1) {
229 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for areas in this unit.');
230 return;
231 }
232 callback();
233 });
234 };
236 function requireIssuePrivilege(conn, req, res, issue_id, callback) {
237 var query = new selector.Selector('privilege');
238 query.join('area', null, 'area.unit_id = privilege.unit_id');
239 query.join('issue', null, 'issue.area_id = area.id');
240 query.addField('NULL');
241 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
242 query.addWhere(['issue.id = ?', issue_id ]);
243 query.addWhere('privilege.voting_right');
244 query.forShareOf('privilege');
245 db.query(conn, req, res, query, function(result, conn) {
246 if (result.rows.length != 1) {
247 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for issues in this unit.');
248 return;
249 }
250 callback();
251 });
252 };
254 function requireInitiativePrivilege(conn, req, res, initiative_id, callback) {
255 var query = new selector.Selector('privilege');
256 query.join('area', null, 'area.unit_id = privilege.unit_id');
257 query.join('issue', null, 'issue.area_id = area.id');
258 query.join('initiative', null, 'initiative.issue_id = issue.id');
259 query.addField('NULL');
260 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
261 query.addWhere(['initiative.id = ?', initiative_id ]);
262 query.addWhere('privilege.voting_right');
263 query.forShareOf('privilege');
264 db.query(conn, req, res, query, function(result, conn) {
265 if (result.rows.length != 1) {
266 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for initiatives in this unit.');
267 return;
268 }
269 callback();
270 });
271 };
273 function requireIssueState(conn, req, res, issue_id, required_states, callback) {
274 var query = new selector.Selector('issue');
275 query.addField('NULL');
276 query.addWhere(['issue.id = ?', issue_id]);
277 query.addWhere(['issue.state IN (??)', required_states]);
278 query.forUpdateOf('issue');
279 db.query(conn, req, res, query, function(result, conn) {
280 if (result.rows.length != 1) {
281 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
282 return;
283 }
284 callback();
285 });
286 };
288 function requireIssueStateForInitiative(conn, req, res, initiative_id, required_states, callback) {
289 var query = new selector.Selector('issue');
290 query.join('initiative', null, 'initiative.issue_id = issue.id');
291 query.addField('NULL');
292 query.addWhere(['initiative.id = ?', initiative_id]);
293 query.addWhere(['issue.state IN (??)', required_states]);
294 query.forUpdateOf('issue');
295 db.query(conn, req, res, query, function(result, conn) {
296 if (result.rows.length != 1) {
297 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
298 return;
299 }
300 callback();
301 });
302 }
304 function requireContingentLeft(conn, req, res, is_initiative, callback) {
305 var query = new selector.Selector('member_contingent_left');
306 query.addField('NULL');
307 query.addWhere(['member_contingent_left.member_id = ?', req.current_member_id]);
308 query.addWhere('member_contingent_left.text_entries_left >= 1');
309 if (is_initiative) {
310 query.addWhere('member_contingent_left.initiatives_left >= 1');
311 }
312 db.query(conn, req, res, query, function(result, conn) {
313 if (result.rows.length != 1) {
314 respond('json', conn, req, res, 'forbidden', null, 'Contingent empty.');
315 return;
316 }
317 callback();
318 });
319 }
321 // ==========================================================================
322 // GET methods
323 // ==========================================================================
326 exports.get = {
328 // startpage (html) for users
329 // currently used for implementing public alpha test
330 '/': function (conn, req, res, params) {
332 var html = [];
333 html.push('<h2>welcome to lfapi public developer alpha test</h2>');
334 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>');
335 html.push('<h2>developer registration</h2>');
336 html.push('<p>To register as developer and receive an account, please send an email to beta20120312@public-software-group.org and you\'ll receive an invitation to the testing system. Read access is available without registering an account.</p>');
337 html.push('<h2>how to use</h2>');
338 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>')
339 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>');
340 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>');
341 html.push('<h2>questions and suggestions</h2>');
342 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>');
343 /*
344 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 />');
345 html.push('<form action="register_test" method="POST">');
346 html.push('<label for="name">Your name:</label> <input type="text" id="name" name="name" /> &nbsp; &nbsp; ');
347 html.push('<label for="email">Email address:</label> <input type="text" id="email" name="email" /> &nbsp; &nbsp; ');
348 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>');
349 html.push('<br />');
350 html.push('<br />');
351 html.push('<div style="border: 2px solid #c00000; background-color: #ffa0a0; padding: 1ex;">');
352 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>!');
353 html.push('<br />');
354 html.push('<br />');
355 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 />');
356 html.push('</div>');
357 html.push('<br />');
358 html.push('<input type="submit" value="Register account" />');
359 */
360 respond('html', null, req, res, 'ok', html.join(''));
361 },
363 // temporary method to implement public alpha test
364 '/register_test_confirm': function (conn, req, res, params) {
365 var secret = params.secret;
367 var query = new selector.Selector('member');
368 query.addField('member.id, member.notify_email_unconfirmed');
369 query.addWhere(['member.notify_email_secret = ?', secret]);
370 db.query(conn, req, res, query, function (result, conn) {
371 var member = result.rows[0];
372 if (member) {
373 var query = new selector.SQLUpdate('member');
374 query.addValues({
375 notify_email: member.notify_email_unconfirmed,
376 notify_email_secret: null,
377 notify_email_unconfirmed: null,
378 active: true,
379 activated: 'now',
380 active: true,
381 last_activity: 'now',
382 locked: false
383 });
384 query.addWhere(['id = ?', member.id]);
385 db.query(conn, req, res, query, function (err, result) {
386 respond('html', conn, req, res, 'ok', 'Account activated: ');
387 });
388 } else {
389 respond('html', conn, req, res, 'forbidden', 'Secret not valid or already used.');
390 }
391 })
392 },
394 '/info': function (conn, req, res, params) {
395 requireAccessLevel(conn, req, res, 'anonymous', function() {
396 var query = new selector.Selector();
397 query.from('"liquid_feedback_version"');
398 query.addField('"liquid_feedback_version".*');
399 db.query(conn, req, res, query, function (result, conn) {
400 var liquid_feedback_version = result.rows[0];
401 var query = new selector.Selector();
402 query.from('"system_setting"');
403 query.addField('"member_ttl"');
404 db.query(conn, req, res, query, function (result, conn) {
405 var member_ttl = null;
406 if (result.rows[0]) {
407 member_ttl = result.rows[0].member_ttl;
408 };
409 respond('json', conn, req, res, 'ok', {
410 core_version: liquid_feedback_version.string,
411 api_version: api_version,
412 current_access_level: req.current_member_id ? 'member' : req.current_access_level,
413 current_member_id: req.current_member_id,
414 member_ttl: member_ttl,
415 settings: config.settings
416 });
417 });
418 });
419 });
420 },
422 '/member_count': function (conn, req, res, params) {
423 requireAccessLevel(conn, req, res, 'anonymous', function() {
424 var query = new selector.Selector();
425 query.from('"member_count"');
426 query.addField('"member_count".*');
427 db.query(conn, req, res, query, function (result, conn) {
428 var member_count = result.rows[0];
429 respond('json', conn, req, res, 'ok', {
430 total_count: member_count.total_count,
431 calculated: member_count.calculated
432 });
433 });
434 });
435 },
437 '/contingent': function (conn, req, res, params) {
438 requireAccessLevel(conn, req, res, 'anonymous', function() {
439 var query = new selector.Selector();
440 query.from('"contingent"');
441 query.addField('"contingent".*');
442 db.query(conn, req, res, query, function (result, conn) {
443 respond('json', conn, req, res, 'ok', { result: result.rows });
444 });
445 });
446 },
448 '/contingent_left': function (conn, req, res, params) {
449 requireAccessLevel(conn, req, res, 'member', function() {
450 var query = new selector.Selector();
451 query.from('"member_contingent_left"');
452 query.addField('"member_contingent_left".text_entries_left');
453 query.addField('"member_contingent_left".initiatives_left');
454 query.addWhere(['member_id = ?', req.current_member_id]);
455 db.query(conn, req, res, query, function (result, conn) {
456 respond('json', conn, req, res, 'ok', { result: result.rows[0] });
457 });
458 });
459 },
461 '/member': function (conn, req, res, params) {
462 requireAccessLevel(conn, req, res, 'pseudonym', function() {
463 var query = new selector.Selector();
464 query.from('"member"');
465 if (req.current_access_level == 'pseudonym' && !req.current_member_id ) {
466 fields.addObjectFields(query, 'member', 'member_pseudonym');
467 } else {
468 fields.addObjectFields(query, 'member');
469 }
470 general_params.addMemberOptions(req, query, params);
471 query.addOrderBy('"member"."id"');
472 general_params.addLimitAndOffset(query, params);
473 db.query(conn, req, res, query, function (result, conn) {
474 respond('json', conn, req, res, 'ok', { result: result.rows });
475 });
476 });
477 },
479 '/member_history': function (conn, req, res, params) {
480 requireAccessLevel(conn, req, res, 'full', function() {
481 var query = new selector.Selector();
482 query.from('"member_history" JOIN "member" ON "member"."id" = "member_history"."member_id"');
483 query.addField('"member_history".*');
484 general_params.addMemberOptions(req, query, params);
485 query.addOrderBy('member_history.id');
486 general_params.addLimitAndOffset(query, params);
487 db.query(conn, req, res, query, function (member_history_result, conn) {
488 var result = { result: member_history_result.rows }
489 includes = [];
490 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
491 addRelatedData(conn, req, res, result, includes);
492 });
493 });
494 },
496 '/member_image': function (conn, req, res, params) {
497 requireAccessLevel(conn, req, res, 'full', function() {
498 var query = new selector.Selector();
499 query.from('"member_image" JOIN "member" ON "member"."id" = "member_image"."member_id"');
500 query.addField('"member_image"."member_id"');
501 query.addField('"member_image"."image_type"');
502 query.addField('"member_image"."scaled"');
503 query.addField('"member_image"."content_type"');
504 query.addField('encode("member_image"."data", \'base64\')', 'data');
505 query.addWhere('member_image.scaled');
506 if (params.type == "avatar") {
507 query.addWhere('member_image.image_type = \'avatar\'');
508 } else if (params.type == "photo") {
509 query.addWhere('member_image.image_type = \'photo\'');
510 }
511 general_params.addMemberOptions(req, query, params);
512 query.addOrderBy = ['member_image.member_id, member_image.image_type'];
513 db.query(conn, req, res, query, function (result, conn) {
514 respond('json', conn, req, res, 'ok', { result: result.rows });
515 });
516 });
517 },
519 '/contact': function (conn, req, res, params) {
520 requireAccessLevel(conn, req, res, 'pseudonym', function() {
521 var query = new selector.Selector();
522 query.from('contact JOIN member ON member.id = contact.member_id');
523 query.addField('"contact".*');
524 if (req.current_member_id) {
525 // public or own for members
526 query.addWhere(['"contact"."public" OR "contact"."member_id" = ?', req.current_member_id]);
527 } else {
528 // public for everybody
529 query.addWhere('"contact"."public"');
530 }
531 general_params.addMemberOptions(req, query, params);
532 query.addOrderBy('"contact"."id"');
533 general_params.addLimitAndOffset(query, params);
534 db.query(conn, req, res, query, function (result, conn) {
535 respond('json', conn, req, res, 'ok', { result: result.rows });
536 });
537 });
538 },
540 '/privilege': function (conn, req, res, params) {
541 requireAccessLevel(conn, req, res, 'pseudonym', function() {
542 var query = new selector.Selector();
543 query.from('privilege JOIN member ON member.id = privilege.member_id JOIN unit ON unit.id = privilege.unit_id');
544 query.addField('privilege.*');
545 general_params.addUnitOptions(req, query, params);
546 general_params.addMemberOptions(req, query, params);
547 query.addOrderBy('privilege.unit_id, privilege.member_id');
548 general_params.addLimitAndOffset(query, params);
549 db.query(conn, req, res, query, function (privilege_result, conn) {
550 var result = { result: privilege_result.rows }
551 includes = [];
552 if (params.include_units) includes.push({ clazz: 'unit', objects: 'result'});
553 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
554 addRelatedData(conn, req, res, result, includes);
555 });
556 });
557 },
559 '/policy': function (conn, req, res, params) {
560 requireAccessLevel(conn, req, res, 'anonymous', function() {
561 var query = new selector.Selector();
562 query.from('"policy"');
563 query.addField('"policy".*');
564 general_params.addPolicyOptions(req, query, params);
565 query.addOrderBy('"policy"."index"');
566 general_params.addLimitAndOffset(query, params);
567 db.query(conn, req, res, query, function (result, conn) {
568 respond('json', conn, req, res, 'ok', { result: result.rows });
569 });
570 });
571 },
573 '/unit': function (conn, req, res, params) {
574 requireAccessLevel(conn, req, res, 'anonymous', function() {
575 var query = new selector.Selector();
576 query.from('"unit"');
577 fields.addObjectFields(query, 'unit');
578 general_params.addUnitOptions(req, query, params);
579 query.addOrderBy('unit.id');
580 general_params.addLimitAndOffset(query, params);
581 db.query(conn, req, res, query, function (result, conn) {
582 respond('json', conn, req, res, 'ok', { result: result.rows });
583 });
584 });
585 },
587 '/area': function (conn, req, res, params) {
588 requireAccessLevel(conn, req, res, 'anonymous', function() {
589 var query = new selector.Selector();
590 query.from('area JOIN unit ON area.unit_id = unit.id');
591 fields.addObjectFields(query, 'area');
592 general_params.addAreaOptions(req, query, params);
593 query.addOrderBy('area.id');
594 general_params.addLimitAndOffset(query, params);
595 db.query(conn, req, res, query, function (area_result, conn) {
596 var result = { result: area_result.rows }
597 includes = [];
598 if (params.include_units) includes.push({ clazz: 'unit', objects: 'result'});
599 addRelatedData(conn, req, res, result, includes);
600 });
601 });
602 },
604 '/allowed_policy': function (conn, req, res, params) {
605 requireAccessLevel(conn, req, res, 'anonymous', function() {
606 var query = new selector.Selector();
607 query.from('allowed_policy');
608 query.join('area', null, 'area.id = allowed_policy.area_id');
609 query.join('unit', null, 'unit.id = area.unit_id');
610 query.addField('allowed_policy.*');
611 general_params.addAreaOptions(req, query, params);
612 query.addOrderBy('allowed_policy.area_id, allowed_policy.policy_id');
613 general_params.addLimitAndOffset(query, params);
614 db.query(conn, req, res, query, function (allowed_policy_result, conn) {
615 var result = { result: allowed_policy_result.rows }
616 includes = [];
617 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'result'});
618 if (params.include_areas) includes.push({ clazz: 'area', objects: 'result'});
619 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
620 addRelatedData(conn, req, res, result, includes);
621 });
622 }); },
624 '/membership': function (conn, req, res, params) {
625 requireAccessLevel(conn, req, res, 'pseudonym', function() {
626 var query = new selector.Selector();
627 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');
628 query.addField('membership.*');
629 general_params.addAreaOptions(req, query, params);
630 general_params.addMemberOptions(req, query, params);
631 query.addOrderBy('membership.area_id, membership.member_id');
632 general_params.addLimitAndOffset(query, params);
633 db.query(conn, req, res, query, function (membership_result, conn) {
634 var result = { result: membership_result.rows }
635 includes = [];
636 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
637 if (params.include_areas) includes.push({ clazz: 'area', objects: 'result'});
638 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
639 addRelatedData(conn, req, res, result, includes);
640 });
641 });
642 },
644 '/issue': function (conn, req, res, params) {
645 requireAccessLevel(conn, req, res, 'anonymous', function() {
646 var query = new selector.Selector()
647 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');
648 fields.addObjectFields(query, 'issue');
649 general_params.addIssueOptions(req, query, params);
650 query.addOrderBy('issue.id');
651 general_params.addLimitAndOffset(query, params);
652 db.query(conn, req, res, query, function (issue_result, conn) {
653 var result = { result: issue_result.rows }
654 includes = [];
655 if (params.include_areas) includes.push({ clazz: 'area', objects: 'result'});
656 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
657 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'result' });
658 addRelatedData(conn, req, res, result, includes);
659 });
660 });
661 },
663 '/population': function (conn, req, res, params) {
664 requireAccessLevel(conn, req, res, 'pseudonym', function() {
665 var query = new selector.Selector();
666 if (params.delegating == '1') {
667 query.from('delegating_population_snapshot', 'population');
668 if (params.delegate_member_id) {
669 query.addWhere(['population.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
670 }
671 if (params.direct_delegate_member_id) {
672 query.addWhere(['population.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
673 }
674 } else {
675 query.from('direct_population_snapshot', 'population');
676 }
677 switch (params.snapshot) {
678 case 'latest':
679 query.addWhere('population.event = issue.latest_snapshot_event');
680 break;
682 case 'end_of_admission':
683 case 'half_freeze':
684 case 'full_freeze':
685 query.addWhere(['population.event = ?', params.snapshot]);
686 break;
688 default:
689 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
690 return;
692 };
693 query.addField('population.*');
694 query.join('member', null, 'member.id = population.member_id');
695 query.join('issue', null, 'population.issue_id = issue.id');
696 query.join('policy', null, 'policy.id = issue.policy_id');
697 query.join('area', null, 'area.id = issue.area_id');
698 query.join('unit', null, 'area.unit_id = unit.id');
699 general_params.addMemberOptions(req, query, params);
700 general_params.addIssueOptions(req, query, params);
701 query.addOrderBy('population.issue_id, population.member_id');
702 general_params.addLimitAndOffset(query, params);
703 db.query(conn, req, res, query, function (population_result, conn) {
704 var result = { result: population_result.rows }
705 includes = [];
706 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
707 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'result'});
708 if (params.include_areas) includes.push({ clazz: 'area', objects: 'areas'});
709 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
710 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
711 addRelatedData(conn, req, res, result, includes);
712 });
713 });
714 },
716 '/interest': function (conn, req, res, params) {
717 requireAccessLevel(conn, req, res, 'pseudonym', function() {
718 var query = new selector.Selector();
719 if (params.snapshot) {
720 if (params.delegating == '1') {
721 query.from('delegating_interest_snapshot', 'interest');
722 if (params.delegate_member_id) {
723 query.addWhere(['interest.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
724 }
725 if (params.direct_delegate_member_id) {
726 query.addWhere(['interest.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
727 }
728 } else {
729 query.from('direct_interest_snapshot', 'interest');
730 }
731 switch (params.snapshot) {
732 case 'latest':
733 query.addWhere('interest.event = issue.latest_snapshot_event');
734 break;
736 case 'end_of_admission':
737 case 'half_freeze':
738 case 'full_freeze':
739 query.addWhere(['interest.event = ?', params.snapshot]);
740 break;
742 default:
743 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
744 return;
746 };
747 } else {
748 if (! req.current_member_id) {
749 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
750 return;
751 };
752 query.from('interest');
753 query.addWhere(['interest.member_id = ?', req.current_member_id]);
754 }
755 query.addField('interest.*');
756 query.join('member', null, 'member.id = interest.member_id');
757 query.join('issue', null, 'interest.issue_id = issue.id');
758 query.join('policy', null, 'policy.id = issue.policy_id');
759 query.join('area', null, 'area.id = issue.area_id');
760 query.join('unit', null, 'area.unit_id = unit.id');
761 general_params.addMemberOptions(req, query, params);
762 general_params.addIssueOptions(req, query, params);
763 query.addOrderBy('interest.issue_id, interest.member_id');
764 general_params.addLimitAndOffset(query, params);
765 db.query(conn, req, res, query, function (interest_result, conn) {
766 var result = { result: interest_result.rows }
767 includes = [];
768 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
769 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'result'});
770 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
771 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
772 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
773 addRelatedData(conn, req, res, result, includes);
774 });
775 });
776 },
778 '/issue_comment': function (conn, req, res, params) {
779 requireAccessLevel(conn, req, res, 'pseudonym', function() {
780 var query = new selector.Selector();
781 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');
782 query.addField('issue_comment.*');
783 general_params.addMemberOptions(req, query, params);
784 general_params.addIssueOptions(req, query, params);
785 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
786 general_params.addLimitAndOffset(query, params);
787 db.query(conn, req, res, query, function (issue_comment_result, conn) {
788 var result = { result: issue_comment_result.rows }
789 includes = [];
790 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
791 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'result'});
792 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
793 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
794 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
795 addRelatedData(conn, req, res, result, includes);
796 });
797 });
798 },
800 '/initiative': function (conn, req, res, params) {
801 requireAccessLevel(conn, req, res, 'anonymous', function() {
802 var query = new selector.Selector();
803 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');
804 fields.addObjectFields(query, 'initiative');
805 query.addOrderBy('initiative.id');
806 general_params.addInitiativeOptions(req, query, params);
807 general_params.addLimitAndOffset(query, params);
808 db.query(conn, req, res, query, function (initiative_result, conn) {
809 var result = { result: initiative_result.rows }
810 includes = [];
811 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'result'});
812 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
813 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
814 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
815 addRelatedData(conn, req, res, result, includes);
816 });
817 });
818 },
820 '/initiator': function (conn, req, res, params) {
821 requireAccessLevel(conn, req, res, 'pseudonym', function() {
822 var fields = ['initiator.initiative_id', 'initiator.member_id'];
823 var query = new selector.Selector();
824 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');
825 query.addWhere('initiator.accepted');
826 fields.forEach( function(field) {
827 query.addField(field, null, ['grouped']);
828 });
829 general_params.addMemberOptions(req, query, params);
830 general_params.addInitiativeOptions(req, query, params);
831 query.addOrderBy('initiator.initiative_id, initiator.member_id');
832 general_params.addLimitAndOffset(query, params);
833 db.query(conn, req, res, query, function (initiator, conn) {
834 var result = { result: initiator.rows }
835 includes = [];
836 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
837 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
838 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
839 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
840 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
841 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
842 addRelatedData(conn, req, res, result, includes);
843 });
844 });
845 },
848 '/supporter': function (conn, req, res, params) {
849 requireAccessLevel(conn, req, res, 'pseudonym', function() {
850 var query = new selector.Selector();
851 if (params.snapshot) {
853 query.from('direct_supporter_snapshot', 'supporter');
854 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');
856 if (params.delegating == '1') {
857 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');
858 query.join('member', null, 'member.id = interest.member_id');
859 if (params.delegate_member_id) {
860 query.addWhere(['interest.delegate_member_ids @> array[?::int]', params.delegate_member_id]);
861 }
862 if (params.direct_delegate_member_id) {
863 query.addWhere(['interest.delegate_member_ids[1] = ?', params.direct_delegate_member_id]);
864 }
865 } else {
866 query.join('direct_interest_snapshot', 'interest', 'interest.issue_id = initiative.issue_id AND interest.member_id = supporter.member_id AND interest.event = supporter.event');
867 query.join('member', null, 'member.id = supporter.member_id');
868 query.addField('supporter.informed, supporter.satisfied');
869 }
871 query.addField('interest.*')
872 query.addField('supporter.initiative_id');
874 switch (params.snapshot) {
875 case 'latest':
876 query.addWhere('supporter.event = issue.latest_snapshot_event');
877 break;
879 case 'end_of_admission':
880 case 'half_freeze':
881 case 'full_freeze':
882 query.addWhere(['supporter.event = ?', params.snapshot]);
883 break;
885 default:
886 respond('json', conn, req, res, 'unprocessable', null, 'Invalid snapshot type');
887 return;
889 };
891 } else {
892 if (! req.current_member_id) {
893 respond('json', conn, req, res, 'unprocessable', null, 'No snapshot type given and not beeing member');
894 return;
895 };
896 query.from('supporter')
897 query.join('member', null, 'member.id = supporter.member_id');
898 query.addField('supporter.*');
899 query.addWhere(['supporter.member_id = ?', req.current_member_id]);
900 }
901 general_params.addMemberOptions(req, query, params);
902 general_params.addInitiativeOptions(req, query, params);
903 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
904 general_params.addLimitAndOffset(query, params);
905 db.query(conn, req, res, query, function (supporter, conn) {
906 var result = { result: supporter.rows }
907 includes = [];
908 if (params.include_members) includes.push({ clazz: 'member', objects: 'result'});
909 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
910 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
911 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
912 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
913 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
914 addRelatedData(conn, req, res, result, includes);
915 });
916 });
917 },
919 '/battle': function (conn, req, res, params) {
920 requireAccessLevel(conn, req, res, 'anonymous', function() {
921 var query = new selector.Selector();
922 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');
923 query.addField('battle.*');
924 general_params.addInitiativeOptions(req, query, params);
925 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
926 general_params.addLimitAndOffset(query, params);
927 db.query(conn, req, res, query, function (result, conn) {
928 var result = { result: result.rows }
929 includes = [];
930 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
931 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
932 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
933 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
934 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
935 addRelatedData(conn, req, res, result, includes);
936 });
937 });
938 },
940 '/draft': function (conn, req, res, params) {
941 requireAccessLevel(conn, req, res, 'anonymous', function() {
942 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.created'];
943 var query = new selector.Selector();
944 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');
945 fields.forEach( function(field) {
946 query.addField(field, null, ['grouped']);
947 });
948 if (req.current_access_level != 'anonymous' || req.current_member_id) {
949 query.addField('draft.author_id', null, ['grouped']);
950 }
951 if (params.draft_id) {
952 query.addWhere('draft.id = ?', params.draft_id);
953 }
954 if (params.current_draft) {
955 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
956 }
957 if (params.render_content == "html") {
958 query.join('rendered_draft', null, 'rendered_draft.draft_id = draft.id AND rendered_draft.format = \'html\'');
959 query.addField('rendered_draft.content', null, ['grouped']);
960 } else {
961 query.addField('draft.content', null, ['grouped']);
962 }
963 general_params.addInitiativeOptions(req, query, params);
964 query.addOrderBy('draft.initiative_id, draft.id');
965 general_params.addLimitAndOffset(query, params);
966 db.query(conn, req, res, query, function (result, conn) {
967 var result = { result: result.rows }
968 includes = [];
969 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
970 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
971 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
972 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
973 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
974 addRelatedData(conn, req, res, result, includes);
975 });
976 });
977 },
979 '/suggestion': function (conn, req, res, params) {
980 requireAccessLevel(conn, req, res, 'anonymous', function() {
981 var query = new selector.Selector();
982 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');
983 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
984 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
985 } else {
986 fields.addObjectFields(query, 'suggestion');
987 }
988 general_params.addSuggestionOptions(req, query, params);
989 query.addOrderBy('suggestion.initiative_id, suggestion.id');
990 general_params.addLimitAndOffset(query, params);
991 db.query(conn, req, res, query, function (result, conn) {
992 var result = { result: result.rows }
993 includes = [];
994 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
995 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
996 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
997 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
998 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
999 addRelatedData(conn, req, res, result, includes);
1000 });
1001 });
1002 },
1004 '/opinion': function (conn, req, res, params) {
1005 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1006 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
1007 var query = new selector.Selector();
1008 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');
1009 fields.forEach( function(field) {
1010 query.addField(field, null, ['grouped']);
1011 });
1012 general_params.addMemberOptions(req, query, params);
1013 general_params.addSuggestionOptions(req, query, params);
1014 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
1015 general_params.addLimitAndOffset(query, params);
1016 db.query(conn, req, res, query, function (result, conn) {
1017 var result = { result: result.rows }
1018 includes = [];
1019 if (params.include_suggestions) includes.push({ clazz: 'suggestion', objects: 'result'});
1020 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'suggestions'});
1021 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'initiatives'});
1022 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
1023 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
1024 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
1025 addRelatedData(conn, req, res, result, includes);
1026 });
1027 });
1028 },
1030 '/delegation': function (conn, req, res, params) {
1031 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1032 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
1033 var query = new selector.Selector();
1034 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');
1035 fields.forEach( function(field) {
1036 query.addField(field, null, ['grouped']);
1037 });
1038 if (params.direction) {
1039 switch (params.direction) {
1040 case 'in':
1041 query.join('member', null, 'member.id = delegation.trustee_id');
1042 break;
1043 case 'out':
1044 query.join('member', null, 'member.id = delegation.truster_id');
1045 break;
1046 default:
1047 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
1049 } else {
1050 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
1052 general_params.addMemberOptions(req, query, params);
1053 general_params.addIssueOptions(req, query, params);
1054 if (params.scope) {
1055 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
1056 };
1057 query.addOrderBy = ['delegation.id'];
1058 general_params.addLimitAndOffset(query, params);
1059 db.query(conn, req, res, query, function (result, conn) {
1060 respond('json', conn, req, res, 'ok', { result: result.rows });
1061 });
1062 });
1063 },
1065 '/voter': function (conn, req, res, params) {
1066 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1067 var query = new selector.Selector();
1068 query.from('direct_voter JOIN member ON member.id = direct_voter.member_id JOIN issue ON issue.id = direct_voter.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');
1069 query.addField('direct_voter.*');
1070 query.addWhere('issue.closed NOTNULL');
1071 general_params.addMemberOptions(req, query, params);
1072 general_params.addIssueOptions(req, query, params);
1073 general_params.addLimitAndOffset(query, params);
1074 db.query(conn, req, res, query, function (result, conn) {
1075 respond('json', conn, req, res, 'ok', { result: result.rows });
1076 });
1077 });
1078 },
1080 '/delegating_voter': function (conn, req, res, params) {
1081 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1082 var query = new selector.Selector();
1083 query.from('delegating_voter JOIN member ON member.id = delegating_voter.member_id JOIN issue ON issue.id = delegating_voter.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');
1084 query.addField('delegating_voter.*');
1085 query.addWhere('issue.closed NOTNULL');
1086 general_params.addMemberOptions(req, query, params);
1087 general_params.addIssueOptions(req, query, params);
1088 general_params.addLimitAndOffset(query, params);
1089 db.query(conn, req, res, query, function (result, conn) {
1090 respond('json', conn, req, res, 'ok', { result: result.rows });
1091 });
1092 });
1093 },
1095 '/vote': function (conn, req, res, params) {
1096 requireAccessLevel(conn, req, res, 'pseudonym', function() {
1097 var query = new selector.Selector();
1098 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');
1099 query.addField('vote.*');
1100 query.addWhere('issue.closed NOTNULL');
1101 general_params.addMemberOptions(req, query, params);
1102 general_params.addInitiativeOptions(req, query, params);
1103 general_params.addLimitAndOffset(query, params);
1104 db.query(conn, req, res, query, function (result, conn) {
1105 respond('json', conn, req, res, 'ok', { result: result.rows });
1106 });
1107 });
1108 },
1110 '/event': function (conn, req, res, params) {
1111 requireAccessLevel(conn, req, res, 'anonymous', function() {
1112 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'];
1113 var query = new selector.Selector();
1114 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');
1115 fields.forEach( function(field) {
1116 query.addField(field, null, ['grouped']);
1117 });
1118 general_params.addMemberOptions(req, query, params);
1119 general_params.addInitiativeOptions(req, query, params);
1120 query.addOrderBy('event.id DESC');
1121 general_params.addLimitAndOffset(query, params);
1122 db.query(conn, req, res, query, function (events, conn) {
1123 var result = { result: events.rows }
1124 includes = [];
1125 if (params.include_initiatives) includes.push({ clazz: 'initiative', objects: 'result'});
1126 if (params.include_issues) includes.push({ clazz: 'issue', objects: 'result'});
1127 if (params.include_areas) includes.push({ clazz: 'area', objects: 'issues'});
1128 if (params.include_units) includes.push({ clazz: 'unit', objects: 'areas'});
1129 if (params.include_policies) includes.push({ clazz: 'policy', objects: 'issues' });
1130 addRelatedData(conn, req, res, result, includes);
1131 });
1132 });
1133 },
1135 // TODO add interfaces for data structure:
1136 // ignored_member requireAccessLevel(conn, req, res, 'member');
1137 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
1138 // setting requireAccessLevel(conn, req, res, 'member');
1140 };
1142 // ==========================================================================
1143 // POST methods
1144 // ==========================================================================
1148 exports.post = {
1150 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
1151 respond('json', conn, req, res, 'ok', { result: params });
1152 }); },
1154 '/register_test': function (conn, req, res, params) {
1155 var understood = params.understood;
1156 var member_login = randomString(16);
1157 var member_name = params.name;
1158 var member_password = randomString(16);
1159 var member_notify_email = params.email;
1160 var member_notify_email_secret = randomString(24);
1161 var api_key_member = randomString(24);
1162 var api_key_full = randomString(24);
1163 var api_key_pseudonym = randomString(24);
1164 var api_key_anonymous = randomString(24);
1166 if (understood != 'understood') {
1167 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
1168 return;
1171 // add member
1172 var query = new selector.SQLInsert('member');
1173 query.addValues({
1174 login: member_login,
1175 password: member_password, // TODO hashing of password
1176 notify_email_unconfirmed: member_notify_email,
1177 notify_email_secret: member_notify_email_secret,
1178 name: member_name
1179 });
1180 query.addReturning('id');
1181 db.query(conn, req, res, query, function (result, conn) {
1182 var member_id = result.rows[0].id;
1184 // add privilege for root unit
1185 var query = new selector.SQLInsert('privilege');
1186 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1187 db.query(conn, req, res, query, function (result, conn) {
1189 var location = params.location;
1190 var unit_id;
1191 switch(location) {
1192 case 'earth':
1193 unit_id = 3;
1194 break;
1195 case 'moon':
1196 unit_id = 4;
1197 break;
1198 case 'mars':
1199 unit_id = 5;
1200 break;
1203 // add privilege for selected planet
1204 var query = new selector.SQLInsert('privilege');
1205 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1206 db.query(conn, req, res, query, function (result, conn) {
1208 // add application key
1209 var query = new selector.SQLInsert('member_application');
1210 query.addValues({
1211 member_id: member_id,
1212 name: 'member',
1213 comment: 'access_level member',
1214 access_level: 'member',
1215 key: api_key_member
1216 });
1217 query.addReturning('id');
1219 db.query(conn, req, res, query, function (result, conn) {
1221 nodemailer.sendmail = '/usr/sbin/sendmail';
1223 // send email to user
1224 nodemailer.send_mail({
1225 sender: config.mail.from,
1226 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1227 to: member_notify_email,
1228 body: "\
1229 Hello " + member_name + ",\n\
1230 \n\
1231 thank you for registering at the public alpha test of the LiquidFeedback\n\
1232 application programming interface. To complete the registration process,\n\
1233 you need to confirm your email address by opening the following URL:\n\
1234 \n\
1235 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1236 \n\
1237 \n\
1238 After you've confirmed your email address, your account will be automatically\n\
1239 activated.\n\
1240 \n\
1241 Your account name is: " + member_name + "\n\
1242 \n\
1243 \n\
1244 You will need the following login and password to register and unregister\n\
1245 applications for your account later. This function is currently not\n\
1246 implemented, but please keep the credentials for future use.\n\
1247 \n\
1248 Account ID: " + member_id + "\n\
1249 Login: " + member_login + "\n\
1250 \n\
1251 \n\
1252 To make you able to actually access the API interface, we added the following\n\
1253 application key with full member access privileges to your account:\n\
1254 \n\
1255 API Key: " + api_key_member + "\n\
1256 \n\
1257 \n\
1258 The base address of the public test is: " + config.public_url_path + "\n\
1259 \n\
1260 The programming interface is described in the LiquidFeedback API\n\
1261 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1262 \n\
1263 The current implementation status of lfapi is published at the LiquidFeedback\n\
1264 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1265 \n\
1266 If you have any questions or suggestions, please use our public mailing list\n\
1267 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1268 \n\
1269 For issues regarding your test account, contact us via email at\n\
1270 lqfb-maintainers@public-software-group.org\n\
1271 \n\
1272 \n\
1273 Sincerely,\n\
1274 \n\
1275 Your LiquidFeedback maintainers",
1276 },
1277 function(err, result){
1278 if(err){ console.log(err); }
1279 });
1281 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1282 });
1283 });
1284 });
1285 });
1286 },
1288 /*
1289 '/register': function (conn, req, res, params) {
1290 var invite_key = params.invite_key;
1291 var login = params.login;
1292 var password = params.password;
1293 var name = params.name;
1294 var notify_email = params.notify_email;
1295 if (!invite_key) {
1296 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1297 return;
1298 };
1299 if (!login) {
1300 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1301 return;
1302 };
1303 if (!password) {
1304 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1305 return;
1306 };
1307 if (!name) {
1308 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1309 return;
1310 };
1311 if (!notify_email) {
1312 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1313 return;
1314 };
1315 // check if akey is valid and get member_id for akey
1316 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) {
1317 if (result.rows.length != 1) {
1318 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1319 return;
1320 };
1321 var member_id = result.rows[0].id;
1322 // check if name is available
1323 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1324 if (result.rows.length > 0) {
1325 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1326 return;
1327 };
1328 // check if login is available
1329 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1330 if (result.rows.length > 0) {
1331 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1332 return;
1333 };
1334 var query = { update: 'member', set: { activation: 'now', active: true, } };
1336 });
1337 });
1338 });
1339 },
1340 */
1342 '/session': function (conn, req, res, params) {
1343 var key = params.key;
1344 if (!key) {
1345 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1346 return;
1347 };
1348 var query = new selector.Selector();
1349 query.from('member');
1350 query.join('member_application', null, 'member_application.member_id = member.id');
1351 query.addField('member.id');
1352 query.addWhere(['member.activated NOTNULL AND member_application.key = ?', key]);
1353 if (params.interactive) {
1354 query.forUpdateOf('member');
1356 db.query(conn, req, res, query, function (result, conn) {
1357 if (result.rows.length != 1) {
1358 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1359 return;
1360 };
1361 var member_id = result.rows[0].id;
1362 var session_key = randomString(16);
1363 req.sessions[session_key] = member_id;
1364 var query;
1365 if (params.interactive) {
1366 query = new selector.SQLUpdate('member');
1367 query.addWhere(['member.id = ?', member_id]);
1368 query.addValues({ last_activity: 'now' });
1370 db.query(conn, req, res, query, function (result, conn) {
1371 respond('json', conn, req, res, 'ok', { session_key: session_key });
1372 });
1373 });
1374 },
1376 '/member': function (conn, req, res, params) {
1377 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1378 requireAccessLevel(conn, req, res, 'member', function() {
1379 var query = new selector.SQLUpdate('member');
1380 query.addWhere(['member.id = ?', req.current_member_id]);
1381 fields.forEach( function(field) {
1382 if (typeof(params[field]) != 'undefined') {
1383 query.addValues({ field: params[field] });
1384 } else {
1385 query.addValues({ field: null });
1387 });
1388 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1389 });
1390 },
1392 '/membership': function (conn, req, res, params) {
1393 requireAccessLevel(conn, req, res, 'member', function() {
1395 // check if area_id is set
1396 var area_id = parseInt(params.area_id);
1397 if (!area_id) {
1398 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1399 return;
1402 // delete membership
1403 if (params.delete) {
1404 var query;
1405 query = new selector.SQLDelete('membership');
1406 query.addWhere(['area_id = ?', area_id]);
1407 query.addWhere(['member_id = ?', req.current_member_id]);
1408 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1410 // add membership
1411 } else {
1413 // lock member for upsert
1414 lockMemberById(conn, req, res, req.current_member_id, function() {
1416 // check and lock privilege
1417 requireAreaPrivilege(conn, req, res, area_id, function() {
1419 // upsert membership
1420 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1421 query.addValues({ area_id: area_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 });
1428 });
1429 },
1431 '/interest': function (conn, req, res, params) {
1432 requireAccessLevel(conn, req, res, 'member', function() {
1433 var query;
1435 // check if issue_id is set
1436 var issue_id = parseInt(params.issue_id);
1437 if (!issue_id) {
1438 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1439 return;
1442 // lock member for upsert
1443 lockMemberById(conn, req, res, req.current_member_id, function() {
1445 // delete interest
1446 if (params.delete) {
1448 // check issue state
1449 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1451 // delete interest
1452 query = new selector.SQLDelete('interest');
1453 query.addWhere(['issue_id = ?', issue_id]);
1454 query.addWhere(['member_id = ?', req.current_member_id]);
1455 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1456 });
1458 // add interest
1459 } else {
1461 // check and lock privilege
1462 requireIssuePrivilege(conn, req, res, issue_id, function() {
1464 // check issue state
1465 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1467 // upsert interest
1468 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1469 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1470 db.query(conn, req, res, query, function(result) {
1471 respond('json', conn, req, res, 'ok');
1472 });
1473 });
1474 });
1475 };
1476 });
1477 });
1478 },
1480 '/issue_comment': function (conn, req, res, params) {
1481 requireAccessLevel(conn, req, res, 'member', function() {
1483 var issue_id = parseInt(params.issue_id);
1484 var formatting_engine = params.formatting_engine
1485 var content = params.content;
1487 if (!issue_id) {
1488 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1489 return;
1492 // delete issue comment
1493 if (params.delete) {
1494 var query;
1495 query = new selector.SQLDelete('issue_comment');
1496 query.addWhere(['issue_id = ?', params.issue_id]);
1497 query.addWhere(['member_id = ?', req.current_member_id]);
1498 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1500 // upsert issue comment
1501 } else {
1503 // check if formatting engine is supplied and valid
1504 if (!formatting_engine) {
1505 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1506 return;
1507 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1508 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1509 return;
1510 };
1512 // check if content is supplied
1513 if (!content) {
1514 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1515 return;
1518 // lock member for upsert
1519 lockMemberById(conn, req, res, req.current_member_id, function() {
1521 // check and lock privilege
1522 requireIssuePrivilege(conn, req, res, issue_id, function() {
1524 // upsert issue comment
1525 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1526 query.addValues({
1527 issue_id: issue_id,
1528 member_id: req.current_member_id,
1529 changed: 'now',
1530 formatting_engine: formatting_engine,
1531 content: content
1532 });
1534 db.query(conn, req, res, query, function(result) {
1535 respond('json', conn, req, res, 'ok');
1536 });
1538 });
1539 });
1543 });
1544 },
1546 '/voting_comment': function (conn, req, res, params) {
1547 requireAccessLevel(conn, req, res, 'member', function() {
1549 var issue_id = parseInt(params.issue_id);
1550 var formatting_engine = params.formatting_engine
1551 var content = params.content;
1553 if (!issue_id) {
1554 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1555 return;
1559 // delete voting comment
1560 if (params.delete) {
1561 var query;
1562 query = new selector.SQLDelete('voting_comment');
1563 query.addWhere(['issue_id = ?', params.issue_id]);
1564 query.addWhere(['member_id = ?', req.current_member_id]);
1565 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1567 // upsert voting comment
1568 } else {
1570 // check if formatting engine is supplied and valid
1571 if (!formatting_engine) {
1572 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1573 return;
1574 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1575 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1576 return;
1577 };
1579 // check if content is supplied
1580 if (!content) {
1581 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1582 return;
1585 // lock member for upsert
1586 lockMemberById(conn, req, res, req.current_member_id, function() {
1588 // check and lock privilege
1589 requireIssuePrivilege(conn, req, res, issue_id, function() {
1591 // check issue state
1592 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1594 // upsert voting comment
1595 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1596 query.addValues({
1597 issue_id: issue_id,
1598 member_id: req.current_member_id,
1599 changed: 'now',
1600 formatting_engine: formatting_engine,
1601 content: content
1602 });
1604 db.query(conn, req, res, query, function(result) {
1605 respond('json', conn, req, res, 'ok');
1606 });
1608 });
1609 });
1610 })
1611 };
1612 });
1613 },
1615 '/supporter': function (conn, req, res, params) {
1616 requireAccessLevel(conn, req, res, 'member', function() {
1617 var initiative_id = parseInt(params.initiative_id);
1618 var draft_id = parseInt(params.draft_id);
1620 // check if needed arguments are supplied
1621 if (!initiative_id) {
1622 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1623 return;
1626 if (!draft_id) {
1627 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1628 return;
1631 // lock member for upsert
1632 lockMemberById(conn, req, res, req.current_member_id, function() {
1634 // delete supporter
1635 if (params.delete) {
1637 // check issue state
1638 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1640 // delete supporter
1641 var query = new selector.SQLDelete('supporter');
1642 query.addWhere(['initiative_id = ?', initiative_id]);
1643 query.addWhere(['member_id = ?', req.current_member_id]);
1644 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1646 });
1648 // upsert supporter
1649 } else {
1651 // check and lock privilege
1652 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1654 // check issue state
1655 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1657 // check if given draft is the current one
1658 var query = new selector.Selector('current_draft');
1659 query.addField('NULL');
1660 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1661 query.addWhere(['current_draft.id = ?', draft_id]);
1663 db.query(conn, req, res, query, function(result) {
1664 if (result.rows.length != 1) {
1665 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1666 return;
1669 // upsert supporter
1670 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1671 query.addValues({
1672 initiative_id: initiative_id,
1673 member_id: req.current_member_id,
1674 draft_id: draft_id
1675 });
1677 db.query(conn, req, res, query, function(result) {
1678 respond('json', conn, req, res, 'ok');
1679 });
1681 });
1682 });
1683 });
1684 };
1685 });
1686 });
1687 },
1689 '/draft': function (conn, req, res, params) {
1690 requireAccessLevel(conn, req, res, 'member', function() {
1691 var area_id = parseInt(params.area_id);
1692 var policy_id = parseInt(params.policy_id);
1693 var issue_id = parseInt(params.issue_id);
1694 var initiative_id = parseInt(params.initiative_id);
1695 var initiative_name = params.initiative_name;
1696 var initiative_discussion_url = params.initiative_discussion_url;
1697 var formatting_engine = params.formatting_engine;
1698 var content = params.content;
1700 if (!initiative_discussion_url) initiative_discussion_url = null;
1702 // check parameters
1703 if (!formatting_engine) {
1704 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1705 return;
1706 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1707 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1708 return;
1709 };
1711 if (!content) {
1712 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1713 return;
1714 };
1716 lockMemberById(conn, req, res, req.current_member_id, function() {
1718 // new draft in new initiative in new issue
1719 if (area_id && !issue_id && !initiative_id) {
1721 // check parameters for new issue
1722 if (!policy_id) {
1723 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1724 return;
1727 if (!initiative_name) {
1728 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1729 return;
1732 requireAreaPrivilege(conn, req, res, area_id, function() {
1734 // check if policy is allowed in this area and if area and policy are active
1735 var query = new selector.Selector();
1736 query.from('allowed_policy');
1737 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1738 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1739 query.addField('NULL');
1740 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1741 db.query(conn, req, res, query, function (result, conn) {
1742 if (result.rows.length != 1) {
1743 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.');
1744 return;
1745 };
1747 // check contingent
1748 requireContingentLeft(conn, req, res, true, function() {
1750 // insert new issue
1751 var query = new selector.SQLInsert('issue');
1752 query.addValues({
1753 area_id: area_id,
1754 policy_id: policy_id
1755 });
1756 query.addReturning('id');
1757 db.query(conn, req, res, query, function(result) {
1758 var issue_id = result.rows[0].id;
1760 // insert new initiative
1761 var query = new selector.SQLInsert('initiative');
1762 query.addValues({
1763 issue_id: issue_id,
1764 name: initiative_name,
1765 discussion_url: initiative_discussion_url
1766 });
1767 query.addReturning('id');
1768 db.query(conn, req, res, query, function(result) {
1769 var initiative_id = result.rows[0].id;
1771 // insert initiator
1772 var query = new selector.SQLInsert('initiator');
1773 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1774 db.query(conn, req, res, query, function(result) {
1776 // insert new draft
1777 var query = new selector.SQLInsert('draft');
1778 query.addValues({
1779 initiative_id: initiative_id,
1780 author_id: req.current_member_id,
1781 formatting_engine: formatting_engine,
1782 content: content
1783 });
1784 query.addReturning('id');
1785 db.query(conn, req, res, query, function (result, conn) {
1786 var draft_id = result.rows[0].id;
1788 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1789 });
1790 });
1791 });
1792 });
1793 });
1794 });
1795 });
1797 // new draft in new initiative in existant issue
1798 } else if (issue_id && !area_id && !initiative_id) {
1800 if (!initiative_name) {
1801 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1802 return;
1805 // check privilege
1806 requireIssuePrivilege(conn, req, res, issue_id, function() {
1808 // check issue state
1809 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1811 // check contingent
1812 requireContingentLeft(conn, req, res, true, function() {
1814 // insert initiative
1815 var query = new selector.SQLInsert('initiative');
1816 query.addValues({
1817 issue_id: issue_id,
1818 name: initiative_name,
1819 discussion_url: initiative_discussion_url
1820 });
1821 query.addReturning('id');
1822 db.query(conn, req, res, query, function(result) {
1823 var initiative_id = result.rows[0].id;
1825 // insert initiator
1826 var query = new selector.SQLInsert('initiator');
1827 query.addValues({
1828 initiative_id: initiative_id,
1829 member_id: req.current_member_id,
1830 accepted: true
1831 });
1832 db.query(conn, req, res, query, function(result) {
1834 // insert draft
1835 var query = new selector.SQLInsert('draft');
1836 query.addValues({
1837 initiative_id: initiative_id,
1838 author_id: req.current_member_id,
1839 formatting_engine: formatting_engine,
1840 content: content
1841 });
1842 query.addReturning('id');
1843 db.query(conn, req, res, query, function (result, conn) {
1845 var draft_id = result.rows[0].id;
1846 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1848 });
1849 });
1850 });
1851 });
1852 });
1853 });
1855 // new draft in existant initiative
1856 } else if (initiative_id && !area_id && !issue_id ) {
1858 // check privilege
1859 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1861 // check issue state
1862 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1865 // get initiator
1866 var query = new selector.Selector();
1867 query.from('initiator');
1868 query.addField('accepted');
1869 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1870 db.query(conn, req, res, query, function (result, conn) {
1872 // if member is not initiator, deny creating new draft
1873 if (result.rows.length != 1) {
1874 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1875 return;
1877 var initiator = result.rows[0];
1878 if (!initiator.accepted) {
1879 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.');
1880 return;
1881 };
1883 // check contingent
1884 requireContingentLeft(conn, req, res, false, function() {
1886 // insert new draft
1887 var query = new selector.SQLInsert('draft');
1888 query.addValues({
1889 initiative_id: initiative_id,
1890 author_id: req.current_member_id,
1891 formatting_engine: formatting_engine,
1892 content: content
1893 });
1894 query.addReturning('id');
1895 db.query(conn, req, res, query, function (result, conn) {
1897 var draft_id = result.rows[0].id;
1898 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1899 });
1900 });
1901 });
1902 });
1903 });
1905 // none of them (invalid request)
1906 } else {
1907 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1908 };
1910 });
1911 });
1912 },
1914 '/suggestion': function (conn, req, res, params) {
1915 requireAccessLevel(conn, req, res, 'member', function() {
1916 // TODO
1917 });
1918 },
1920 '/opinion': function (conn, req, res, params) {
1921 requireAccessLevel(conn, req, res, 'member', function() {
1922 // TODO
1923 });
1924 },
1926 '/delegation': function (conn, req, res, params) {
1927 requireAccessLevel(conn, req, res, 'member', function() {
1928 var unit_id = parseInt(params.unit_id);
1929 var area_id = parseInt(params.area_id);
1930 var issue_id = parseInt(params.issue_id);
1931 var trustee_id;
1933 if (params.trustee_id == '') {
1934 trustee_id = null;
1935 } else {
1936 trustee_id = parseInt(params.trustee_id);
1939 lockMemberById(conn, req, res, req.current_member_id, function() {
1941 if (params.delete) {
1942 var query = new selector.SQLDelete('delegation')
1943 if (unit_id && !area_id && !issue_id) {
1944 query.addWhere(['unit_id = ?', unit_id]);
1945 } else if (!unit_id && area_id && !issue_id) {
1946 query.addWhere(['area_id = ?', area_id]);
1947 } else if (!unit_id && !area_id && issue_id) {
1948 query.addWhere(['issue_id = ?', issue_id]);
1949 } else {
1950 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1951 return;
1953 query.addWhere(['truster_id = ?', req.current_member_id]);
1954 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1955 } else {
1956 var query = new selector.Upserter('delegation', ['truster_id']);
1957 query.addValues({
1958 truster_id: req.current_member_id,
1959 trustee_id: trustee_id
1960 });
1961 if (unit_id && !area_id && !issue_id) {
1963 // check privilege
1964 requireUnitPrivilege(conn, req, res, unit_id, function() {
1966 query.addKeys(['unit_id'])
1967 query.addValues({ unit_id: unit_id, scope: 'unit' });
1968 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1969 });
1971 } else if (!unit_id && area_id && !issue_id) {
1973 // check privilege
1974 requireAreaPrivilege(conn, req, res, area_id, function() {
1976 query.addKeys(['area_id'])
1977 query.addValues({ area_id: area_id, scope: 'area' });
1978 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1979 });
1981 } else if (!unit_id && !area_id && issue_id) {
1983 // check privilege
1984 requireIssuePrivilege(conn, req, res, issue_id, function() {
1986 // check issue state
1987 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1989 query.addKeys(['issue_id'])
1990 query.addValues({ issue_id: issue_id, scope: 'issue' });
1991 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1992 });
1993 });
1994 } else {
1995 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1996 return;
2000 });
2002 });
2003 },
2005 };

Impressum / About Us