lfapi

view lfapi/main.js @ 41:43c90f066b52

Fixed wrong fields in suggestion field definitions
author bsw
date Fri Mar 22 16:16:46 2013 +0100 (2013-03-22)
parents 28865db108c9
children
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 'not found':
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 var tmp = {};
1384 tmp[field] = params[field];
1385 query.addValues(tmp);
1386 } else {
1387 var tmp = {};
1388 tmp[field] = null;
1389 query.addValues(tmp);
1391 });
1392 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1393 });
1394 },
1396 '/membership': function (conn, req, res, params) {
1397 requireAccessLevel(conn, req, res, 'member', function() {
1399 // check if area_id is set
1400 var area_id = parseInt(params.area_id);
1401 if (!area_id) {
1402 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1403 return;
1406 // delete membership
1407 if (params.delete) {
1408 var query;
1409 query = new selector.SQLDelete('membership');
1410 query.addWhere(['area_id = ?', area_id]);
1411 query.addWhere(['member_id = ?', req.current_member_id]);
1412 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1414 // add membership
1415 } else {
1417 // lock member for upsert
1418 lockMemberById(conn, req, res, req.current_member_id, function() {
1420 // check and lock privilege
1421 requireAreaPrivilege(conn, req, res, area_id, function() {
1423 // upsert membership
1424 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1425 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1426 db.query(conn, req, res, query, function(result) {
1427 respond('json', conn, req, res, 'ok');
1428 });
1429 });
1430 });
1432 });
1433 },
1435 '/interest': function (conn, req, res, params) {
1436 requireAccessLevel(conn, req, res, 'member', function() {
1437 var query;
1439 // check if issue_id is set
1440 var issue_id = parseInt(params.issue_id);
1441 if (!issue_id) {
1442 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1443 return;
1446 // lock member for upsert
1447 lockMemberById(conn, req, res, req.current_member_id, function() {
1449 // delete interest
1450 if (params.delete) {
1452 // check issue state
1453 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1455 // delete interest
1456 query = new selector.SQLDelete('interest');
1457 query.addWhere(['issue_id = ?', issue_id]);
1458 query.addWhere(['member_id = ?', req.current_member_id]);
1459 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1460 });
1462 // add interest
1463 } else {
1465 // check and lock privilege
1466 requireIssuePrivilege(conn, req, res, issue_id, function() {
1468 // check issue state
1469 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1471 // upsert interest
1472 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1473 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1474 db.query(conn, req, res, query, function(result) {
1475 respond('json', conn, req, res, 'ok');
1476 });
1477 });
1478 });
1479 };
1480 });
1481 });
1482 },
1484 '/issue_comment': function (conn, req, res, params) {
1485 requireAccessLevel(conn, req, res, 'member', function() {
1487 var issue_id = parseInt(params.issue_id);
1488 var formatting_engine = params.formatting_engine
1489 var content = params.content;
1491 if (!issue_id) {
1492 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1493 return;
1496 // delete issue comment
1497 if (params.delete) {
1498 var query;
1499 query = new selector.SQLDelete('issue_comment');
1500 query.addWhere(['issue_id = ?', params.issue_id]);
1501 query.addWhere(['member_id = ?', req.current_member_id]);
1502 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1504 // upsert issue comment
1505 } else {
1507 // check if formatting engine is supplied and valid
1508 if (!formatting_engine) {
1509 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1510 return;
1511 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1512 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1513 return;
1514 };
1516 // check if content is supplied
1517 if (!content) {
1518 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1519 return;
1522 // lock member for upsert
1523 lockMemberById(conn, req, res, req.current_member_id, function() {
1525 // check and lock privilege
1526 requireIssuePrivilege(conn, req, res, issue_id, function() {
1528 // upsert issue comment
1529 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1530 query.addValues({
1531 issue_id: issue_id,
1532 member_id: req.current_member_id,
1533 changed: 'now',
1534 formatting_engine: formatting_engine,
1535 content: content
1536 });
1538 db.query(conn, req, res, query, function(result) {
1539 respond('json', conn, req, res, 'ok');
1540 });
1542 });
1543 });
1547 });
1548 },
1550 '/voting_comment': function (conn, req, res, params) {
1551 requireAccessLevel(conn, req, res, 'member', function() {
1553 var issue_id = parseInt(params.issue_id);
1554 var formatting_engine = params.formatting_engine
1555 var content = params.content;
1557 if (!issue_id) {
1558 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1559 return;
1563 // delete voting comment
1564 if (params.delete) {
1565 var query;
1566 query = new selector.SQLDelete('voting_comment');
1567 query.addWhere(['issue_id = ?', params.issue_id]);
1568 query.addWhere(['member_id = ?', req.current_member_id]);
1569 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1571 // upsert voting comment
1572 } else {
1574 // check if formatting engine is supplied and valid
1575 if (!formatting_engine) {
1576 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1577 return;
1578 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1579 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1580 return;
1581 };
1583 // check if content is supplied
1584 if (!content) {
1585 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1586 return;
1589 // lock member for upsert
1590 lockMemberById(conn, req, res, req.current_member_id, function() {
1592 // check and lock privilege
1593 requireIssuePrivilege(conn, req, res, issue_id, function() {
1595 // check issue state
1596 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1598 // upsert voting comment
1599 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1600 query.addValues({
1601 issue_id: issue_id,
1602 member_id: req.current_member_id,
1603 changed: 'now',
1604 formatting_engine: formatting_engine,
1605 content: content
1606 });
1608 db.query(conn, req, res, query, function(result) {
1609 respond('json', conn, req, res, 'ok');
1610 });
1612 });
1613 });
1614 })
1615 };
1616 });
1617 },
1619 '/supporter': function (conn, req, res, params) {
1620 requireAccessLevel(conn, req, res, 'member', function() {
1621 var initiative_id = parseInt(params.initiative_id);
1622 var draft_id = parseInt(params.draft_id);
1624 // check if needed arguments are supplied
1625 if (!initiative_id) {
1626 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1627 return;
1630 if (!draft_id) {
1631 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1632 return;
1635 // lock member for upsert
1636 lockMemberById(conn, req, res, req.current_member_id, function() {
1638 // delete supporter
1639 if (params.delete) {
1641 // check issue state
1642 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1644 // delete supporter
1645 var query = new selector.SQLDelete('supporter');
1646 query.addWhere(['initiative_id = ?', initiative_id]);
1647 query.addWhere(['member_id = ?', req.current_member_id]);
1648 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1650 });
1652 // upsert supporter
1653 } else {
1655 // check and lock privilege
1656 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1658 // check issue state
1659 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1661 // check if given draft is the current one
1662 var query = new selector.Selector('current_draft');
1663 query.addField('NULL');
1664 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1665 query.addWhere(['current_draft.id = ?', draft_id]);
1667 db.query(conn, req, res, query, function(result) {
1668 if (result.rows.length != 1) {
1669 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1670 return;
1673 // upsert supporter
1674 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1675 query.addValues({
1676 initiative_id: initiative_id,
1677 member_id: req.current_member_id,
1678 draft_id: draft_id
1679 });
1681 db.query(conn, req, res, query, function(result) {
1682 respond('json', conn, req, res, 'ok');
1683 });
1685 });
1686 });
1687 });
1688 };
1689 });
1690 });
1691 },
1693 '/draft': function (conn, req, res, params) {
1694 requireAccessLevel(conn, req, res, 'member', function() {
1695 var area_id = parseInt(params.area_id);
1696 var policy_id = parseInt(params.policy_id);
1697 var issue_id = parseInt(params.issue_id);
1698 var initiative_id = parseInt(params.initiative_id);
1699 var initiative_name = params.initiative_name;
1700 var initiative_discussion_url = params.initiative_discussion_url;
1701 var formatting_engine = params.formatting_engine;
1702 var content = params.content;
1704 if (!initiative_discussion_url) initiative_discussion_url = null;
1706 // check parameters
1707 if (!formatting_engine) {
1708 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1709 return;
1710 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1711 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1712 return;
1713 };
1715 if (!content) {
1716 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1717 return;
1718 };
1720 lockMemberById(conn, req, res, req.current_member_id, function() {
1722 // new draft in new initiative in new issue
1723 if (area_id && !issue_id && !initiative_id) {
1725 // check parameters for new issue
1726 if (!policy_id) {
1727 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1728 return;
1731 if (!initiative_name) {
1732 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1733 return;
1736 requireAreaPrivilege(conn, req, res, area_id, function() {
1738 // check if policy is allowed in this area and if area and policy are active
1739 var query = new selector.Selector();
1740 query.from('allowed_policy');
1741 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1742 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1743 query.addField('NULL');
1744 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1745 db.query(conn, req, res, query, function (result, conn) {
1746 if (result.rows.length != 1) {
1747 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.');
1748 return;
1749 };
1751 // check contingent
1752 requireContingentLeft(conn, req, res, true, function() {
1754 // insert new issue
1755 var query = new selector.SQLInsert('issue');
1756 query.addValues({
1757 area_id: area_id,
1758 policy_id: policy_id
1759 });
1760 query.addReturning('id');
1761 db.query(conn, req, res, query, function(result) {
1762 var issue_id = result.rows[0].id;
1764 // insert new initiative
1765 var query = new selector.SQLInsert('initiative');
1766 query.addValues({
1767 issue_id: issue_id,
1768 name: initiative_name,
1769 discussion_url: initiative_discussion_url
1770 });
1771 query.addReturning('id');
1772 db.query(conn, req, res, query, function(result) {
1773 var initiative_id = result.rows[0].id;
1775 // insert initiator
1776 var query = new selector.SQLInsert('initiator');
1777 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1778 db.query(conn, req, res, query, function(result) {
1780 // insert new draft
1781 var query = new selector.SQLInsert('draft');
1782 query.addValues({
1783 initiative_id: initiative_id,
1784 author_id: req.current_member_id,
1785 formatting_engine: formatting_engine,
1786 content: content
1787 });
1788 query.addReturning('id');
1789 db.query(conn, req, res, query, function (result, conn) {
1790 var draft_id = result.rows[0].id;
1792 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1793 });
1794 });
1795 });
1796 });
1797 });
1798 });
1799 });
1801 // new draft in new initiative in existant issue
1802 } else if (issue_id && !area_id && !initiative_id) {
1804 if (!initiative_name) {
1805 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1806 return;
1809 // check privilege
1810 requireIssuePrivilege(conn, req, res, issue_id, function() {
1812 // check issue state
1813 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1815 // check contingent
1816 requireContingentLeft(conn, req, res, true, function() {
1818 // insert initiative
1819 var query = new selector.SQLInsert('initiative');
1820 query.addValues({
1821 issue_id: issue_id,
1822 name: initiative_name,
1823 discussion_url: initiative_discussion_url
1824 });
1825 query.addReturning('id');
1826 db.query(conn, req, res, query, function(result) {
1827 var initiative_id = result.rows[0].id;
1829 // insert initiator
1830 var query = new selector.SQLInsert('initiator');
1831 query.addValues({
1832 initiative_id: initiative_id,
1833 member_id: req.current_member_id,
1834 accepted: true
1835 });
1836 db.query(conn, req, res, query, function(result) {
1838 // insert 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', { initiative_id: initiative_id, draft_id: draft_id } );
1852 });
1853 });
1854 });
1855 });
1856 });
1857 });
1859 // new draft in existant initiative
1860 } else if (initiative_id && !area_id && !issue_id ) {
1862 // check privilege
1863 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1865 // check issue state
1866 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1869 // get initiator
1870 var query = new selector.Selector();
1871 query.from('initiator');
1872 query.addField('accepted');
1873 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1874 db.query(conn, req, res, query, function (result, conn) {
1876 // if member is not initiator, deny creating new draft
1877 if (result.rows.length != 1) {
1878 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1879 return;
1881 var initiator = result.rows[0];
1882 if (!initiator.accepted) {
1883 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.');
1884 return;
1885 };
1887 // check contingent
1888 requireContingentLeft(conn, req, res, false, function() {
1890 // insert new draft
1891 var query = new selector.SQLInsert('draft');
1892 query.addValues({
1893 initiative_id: initiative_id,
1894 author_id: req.current_member_id,
1895 formatting_engine: formatting_engine,
1896 content: content
1897 });
1898 query.addReturning('id');
1899 db.query(conn, req, res, query, function (result, conn) {
1901 var draft_id = result.rows[0].id;
1902 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1903 });
1904 });
1905 });
1906 });
1907 });
1909 // none of them (invalid request)
1910 } else {
1911 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1912 };
1914 });
1915 });
1916 },
1918 '/suggestion': function (conn, req, res, params) {
1919 requireAccessLevel(conn, req, res, 'member', function() {
1920 // TODO
1921 });
1922 },
1924 '/opinion': function (conn, req, res, params) {
1925 requireAccessLevel(conn, req, res, 'member', function() {
1926 // TODO
1927 });
1928 },
1930 '/delegation': function (conn, req, res, params) {
1931 requireAccessLevel(conn, req, res, 'member', function() {
1932 var unit_id = parseInt(params.unit_id);
1933 var area_id = parseInt(params.area_id);
1934 var issue_id = parseInt(params.issue_id);
1935 var trustee_id;
1937 if (params.trustee_id == '') {
1938 trustee_id = null;
1939 } else {
1940 trustee_id = parseInt(params.trustee_id);
1943 lockMemberById(conn, req, res, req.current_member_id, function() {
1945 if (params.delete) {
1946 var query = new selector.SQLDelete('delegation')
1947 if (unit_id && !area_id && !issue_id) {
1948 query.addWhere(['unit_id = ?', unit_id]);
1949 } else if (!unit_id && area_id && !issue_id) {
1950 query.addWhere(['area_id = ?', area_id]);
1951 } else if (!unit_id && !area_id && issue_id) {
1952 query.addWhere(['issue_id = ?', issue_id]);
1953 } else {
1954 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1955 return;
1957 query.addWhere(['truster_id = ?', req.current_member_id]);
1958 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1959 } else {
1960 var query = new selector.Upserter('delegation', ['truster_id']);
1961 query.addValues({
1962 truster_id: req.current_member_id,
1963 trustee_id: trustee_id
1964 });
1965 if (unit_id && !area_id && !issue_id) {
1967 // check privilege
1968 requireUnitPrivilege(conn, req, res, unit_id, function() {
1970 query.addKeys(['unit_id'])
1971 query.addValues({ unit_id: unit_id, scope: 'unit' });
1972 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1973 });
1975 } else if (!unit_id && area_id && !issue_id) {
1977 // check privilege
1978 requireAreaPrivilege(conn, req, res, area_id, function() {
1980 query.addKeys(['area_id'])
1981 query.addValues({ area_id: area_id, scope: 'area' });
1982 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1983 });
1985 } else if (!unit_id && !area_id && issue_id) {
1987 // check privilege
1988 requireIssuePrivilege(conn, req, res, issue_id, function() {
1990 // check issue state
1991 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1993 query.addKeys(['issue_id'])
1994 query.addValues({ issue_id: issue_id, scope: 'issue' });
1995 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1996 });
1997 });
1998 } else {
1999 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
2000 return;
2004 });
2006 });
2007 },
2009 };

Impressum / About Us