lfapi

view lfapi/main.js @ 0:ce6f95d23e1c

Initial checkin
author bsw
date Sat Sep 10 23:31:20 2011 +0200 (2011-09-10)
parents
children 9fe872cc376d
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 email = require('mailer');
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 = 500;
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 (conn && typeof(conn) != 'string') conn.drain();
83 if (mode == 'json') {
84 if (! object) object = {};
85 } else if (mode == 'html') {
86 if (! object) object = 'no content';
87 if (err) object = "Error: " + err;
88 }
90 object.status = status;
91 object.error = err;
93 if (mode == 'json') {
94 var body = JSON.stringify(object);
95 var content_type = 'application/json';
96 if (req.params && req.params.callback) {
97 body = req.params.callback + '(' + body + ')';
98 content_type = 'text/javascript';
99 }
100 res.writeHead(
101 http_status,
102 {
103 'Content-Type': content_type,
104 //'Content-Length': body.length
105 }
106 );
107 res.end(body);
108 } else if (mode == 'html') {
109 var body = ['<html><head><title>lfapi</title><style>body { font-family: sans-serif; }</style></head><body>']
110 body.push(object)
111 body.push('</body></html>')
112 body = body.join('');
113 res.writeHead(
114 http_status,
115 {
116 'Content-Type': 'text/html',
117 'Content-Length': body.length
118 }
119 );
120 res.end(body);
121 }
122 })
123 });
124 };
126 exports.respond = respond;
127 db.error_handler = respond;
129 // add requested related data for requests with include_* parameters
130 function addRelatedData(conn, req, res, result, includes) {
131 if (includes.length > 0) {
132 var include = includes.shift();
133 var class = include.class;
134 var objects = result[include.objects];
136 var query;
138 if (objects) {
139 var objects_exists = false;
140 query = new selector.Selector();
141 var ids_hash = {};
142 if (typeof(objects) == 'array') {
143 if (objects.length > 0) {
144 objects_exists = true;
145 objects.forEach( function(object) {
146 if (object[class + "_id"]) {
147 ids_hash[object[class + "_id"]] = true;
148 };
149 });
150 }
151 } else {
152 for (var key in objects) {
153 objects_exists = true;
154 var object = objects[key];
155 if (object[class + "_id"]) {
156 ids_hash[object[class + "_id"]] = true;
157 };
158 };
159 };
161 if (objects_exists) {
162 var ids = [];
163 for (key in ids_hash) {
164 ids.push(key)
165 }
167 query.from(class);
168 query.addWhere([class + '.id IN (??)', ids]);
169 fields.addObjectFields(query, class);
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 (class == 'policy') {
183 result['policies'] = tmp;
184 } else {
185 result[class + '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 // GETT 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>how to use</h2>');
336 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>')
337 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>');
338 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>');
339 html.push('<h2>questions and suggestions</h2>');
340 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>');
341 html.push('<h2>developer registration</h2>');
342 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 />');
343 html.push('<form action="register_test" method="POST">');
344 html.push('<label for="name">Your name:</label> <input type="text" id="name" name="name" /> &nbsp; &nbsp; ');
345 html.push('<label for="email">Email address:</label> <input type="text" id="email" name="email" /> &nbsp; &nbsp; ');
346 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>');
347 html.push('<br />');
348 html.push('<br />');
349 html.push('<div style="border: 2px solid #c00000; background-color: #ffa0a0; padding: 1ex;">');
350 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>!');
351 html.push('<br />');
352 html.push('<br />');
353 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 />');
354 html.push('</div>');
355 html.push('<br />');
356 html.push('<input type="submit" value="Register account" />');
357 respond('html', null, req, res, 'ok', html.join(''));
358 },
360 // temporary method to implement public alpha test
361 '/register_test_confirm': function (conn, req, res, params) {
362 var secret = params.secret;
364 var query = new selector.Selector('member');
365 query.addField('member.id, member.notify_email_unconfirmed');
366 query.addWhere(['member.notify_email_secret = ?', secret]);
367 db.query(conn, req, res, query, function (result, conn) {
368 var member = result.rows[0];
369 if (member) {
370 var query = new selector.SQLUpdate('member');
371 query.addValues({
372 notify_email: member.notify_email_unconfirmed,
373 notify_email_secret: null,
374 notify_email_unconfirmed: null,
375 active: true,
376 activated: 'now',
377 active: true,
378 last_activity: 'now',
379 locked: false
380 });
381 query.addWhere(['id = ?', member.id]);
382 db.query(conn, req, res, query, function (err, result) {
383 respond('html', conn, req, res, 'ok', 'Account activated: ');
384 });
385 } else {
386 respond('html', conn, req, res, 'forbidden', 'Secret not valid or already used.');
387 }
388 })
389 },
391 '/info': function (conn, req, res, params) {
392 requireAccessLevel(conn, req, res, 'anonymous', function() {
393 var query = new selector.Selector();
394 query.from('"liquid_feedback_version"');
395 query.addField('"liquid_feedback_version".*');
396 db.query(conn, req, res, query, function (result, conn) {
397 var liquid_feedback_version = result.rows[0];
398 respond('json', conn, req, res, 'ok', {
399 core_version: liquid_feedback_version.string,
400 api_version: api_version,
401 current_access_level: req.current_member_id ? 'member' : req.current_access_level,
402 current_member_id: req.current_member_id,
403 settings: config.settings
404 });
405 });
406 });
407 },
409 '/member_count': function (conn, req, res, params) {
410 requireAccessLevel(conn, req, res, 'anonymous', function() {
411 var query = new selector.Selector();
412 query.from('"member_count"');
413 query.addField('"member_count".*');
414 db.query(conn, req, res, query, function (result, conn) {
415 var member_count = result.rows[0];
416 respond('json', conn, req, res, 'ok', {
417 member_count: member_count.total_count,
418 member_count_calculated: member_count.calculated
419 });
420 });
421 });
422 },
424 '/contingent': function (conn, req, res, params) {
425 requireAccessLevel(conn, req, res, 'anonymous', function() {
426 var query = new selector.Selector();
427 query.from('"contingent"');
428 query.addField('"contingent".*');
429 db.query(conn, req, res, query, function (result, conn) {
430 respond('json', conn, req, res, 'ok', { result: result.rows });
431 });
432 });
433 },
435 '/contingent_left': function (conn, req, res, params) {
436 requireAccessLevel(conn, req, res, 'member', function() {
437 var query = new selector.Selector();
438 query.from('"member_contingent_left"');
439 query.addField('"member_contingent_left".text_entries_left');
440 query.addField('"member_contingent_left".initiatives_left');
441 query.addWhere(['member_id = ?', req.current_member_id]);
442 db.query(conn, req, res, query, function (result, conn) {
443 respond('json', conn, req, res, 'ok', { result: result.rows[0] });
444 });
445 });
446 },
448 '/member': function (conn, req, res, params) {
449 requireAccessLevel(conn, req, res, 'pseudonym', function() {
450 var query = new selector.Selector();
451 query.from('"member"');
452 if (req.current_access_level == 'pseudonym' && !req.current_member_id ) {
453 fields.addObjectFields(query, 'member', 'member_pseudonym');
454 } else {
455 fields.addObjectFields(query, 'member');
456 }
457 general_params.addMemberOptions(req, query, params);
458 query.addOrderBy('"member"."id"');
459 general_params.addLimitAndOffset(query, params);
460 db.query(conn, req, res, query, function (result, conn) {
461 respond('json', conn, req, res, 'ok', { result: result.rows });
462 });
463 });
464 },
466 '/member_history': function (conn, req, res, params) {
467 requireAccessLevel(conn, req, res, 'full', function() {
468 var query = new selector.Selector();
469 query.from('"member_history" JOIN "member" ON "member"."id" = "member_history"."member_id"');
470 query.addField('"member_history".*');
471 general_params.addMemberOptions(req, query, params);
472 query.addOrderBy('member_history.id');
473 general_params.addLimitAndOffset(query, params);
474 db.query(conn, req, res, query, function (member_history_result, conn) {
475 var result = { result: member_history_result.rows }
476 includes = [];
477 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
478 addRelatedData(conn, req, res, result, includes);
479 });
480 });
481 },
483 '/member_image': function (conn, req, res, params) {
484 requireAccessLevel(conn, req, res, 'full', function() {
485 var query = new selector.Selector();
486 query.from('"member_image" JOIN "member" ON "member"."id" = "member_image"."member_id"');
487 query.addField('"member_image".*');
488 query.addWhere('member_image.scaled');
489 general_params.addMemberOptions(req, query, params);
490 query.addOrderBy = ['member_image.member_id, member_image.image_type'];
491 db.query(conn, req, res, query, function (result, conn) {
492 respond('json', conn, req, res, 'ok', { result: result.rows });
493 });
494 });
495 },
497 '/contact': function (conn, req, res, params) {
498 requireAccessLevel(conn, req, res, 'pseudonym', function() {
499 var query = new selector.Selector();
500 query.from('contact JOIN member ON member.id = contact.member_id');
501 query.addField('"contact".*');
502 if (req.current_member_id) {
503 // public or own for members
504 query.addWhere(['"contact"."public" OR "contact"."member_id" = ?', req.current_member_id]);
505 } else {
506 // public for everybody
507 query.addWhere('"contact"."public"');
508 }
509 general_params.addMemberOptions(req, query, params);
510 query.addOrderBy('"contact"."id"');
511 general_params.addLimitAndOffset(query, params);
512 db.query(conn, req, res, query, function (result, conn) {
513 respond('json', conn, req, res, 'ok', { result: result.rows });
514 });
515 });
516 },
518 '/privilege': function (conn, req, res, params) {
519 requireAccessLevel(conn, req, res, 'pseudonym', function() {
520 var query = new selector.Selector();
521 query.from('privilege JOIN member ON member.id = privilege.member_id JOIN unit ON unit.id = privilege.unit_id');
522 query.addField('privilege.*');
523 general_params.addUnitOptions(req, query, params);
524 general_params.addMemberOptions(req, query, params);
525 query.addOrderBy('privilege.unit_id, privilege.member_id');
526 general_params.addLimitAndOffset(query, params);
527 db.query(conn, req, res, query, function (privilege_result, conn) {
528 var result = { result: privilege_result.rows }
529 includes = [];
530 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
531 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
532 addRelatedData(conn, req, res, result, includes);
533 });
534 });
535 },
537 '/policy': function (conn, req, res, params) {
538 requireAccessLevel(conn, req, res, 'anonymous', function() {
539 var query = new selector.Selector();
540 query.from('"policy"');
541 query.addField('"policy".*');
542 general_params.addPolicyOptions(req, query, params);
543 query.addOrderBy('"policy"."index"');
544 general_params.addLimitAndOffset(query, params);
545 db.query(conn, req, res, query, function (result, conn) {
546 respond('json', conn, req, res, 'ok', { result: result.rows });
547 });
548 });
549 },
551 '/unit': function (conn, req, res, params) {
552 requireAccessLevel(conn, req, res, 'anonymous', function() {
553 var query = new selector.Selector();
554 query.from('"unit"');
555 fields.addObjectFields(query, 'unit');
556 general_params.addUnitOptions(req, query, params);
557 query.addOrderBy('unit.id');
558 general_params.addLimitAndOffset(query, params);
559 db.query(conn, req, res, query, function (result, conn) {
560 respond('json', conn, req, res, 'ok', { result: result.rows });
561 });
562 });
563 },
565 '/area': function (conn, req, res, params) {
566 requireAccessLevel(conn, req, res, 'anonymous', function() {
567 var query = new selector.Selector();
568 query.from('area JOIN unit ON area.unit_id = unit.id');
569 fields.addObjectFields(query, 'area');
570 general_params.addAreaOptions(req, query, params);
571 query.addOrderBy('area.id');
572 general_params.addLimitAndOffset(query, params);
573 db.query(conn, req, res, query, function (area_result, conn) {
574 var result = { result: area_result.rows }
575 includes = [];
576 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
577 addRelatedData(conn, req, res, result, includes);
578 });
579 });
580 },
582 '/allowed_policy': function (conn, req, res, params) {
583 requireAccessLevel(conn, req, res, 'anonymous', function() {
584 var query = new selector.Selector();
585 query.from('allowed_policy');
586 query.join('area', null, 'area.id = allowed_policy.area_id');
587 query.join('unit', null, 'unit.id = area.unit_id');
588 query.addField('allowed_policy.*');
589 general_params.addAreaOptions(req, query, params);
590 query.addOrderBy('allowed_policy.area_id, allowed_policy.policy_id');
591 general_params.addLimitAndOffset(query, params);
592 db.query(conn, req, res, query, function (allowed_policy_result, conn) {
593 var result = { result: allowed_policy_result.rows }
594 includes = [];
595 if (params.include_policies) includes.push({ class: 'policy', objects: 'result'});
596 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
597 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
598 addRelatedData(conn, req, res, result, includes);
599 });
600 }); },
602 '/membership': function (conn, req, res, params) {
603 requireAccessLevel(conn, req, res, 'pseudonym', function() {
604 var query = new selector.Selector();
605 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');
606 query.addField('membership.*');
607 general_params.addAreaOptions(req, query, params);
608 general_params.addMemberOptions(req, query, params);
609 query.addOrderBy('membership.area_id, membership.member_id');
610 general_params.addLimitAndOffset(query, params);
611 db.query(conn, req, res, query, function (membership_result, conn) {
612 var result = { result: membership_result.rows }
613 includes = [];
614 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
615 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
616 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
617 addRelatedData(conn, req, res, result, includes);
618 });
619 });
620 },
622 '/issue': function (conn, req, res, params) {
623 requireAccessLevel(conn, req, res, 'anonymous', function() {
624 var query = new selector.Selector()
625 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');
626 fields.addObjectFields(query, 'issue');
627 general_params.addIssueOptions(req, query, params);
628 query.addOrderBy('issue.id');
629 general_params.addLimitAndOffset(query, params);
630 db.query(conn, req, res, query, function (issue_result, conn) {
631 var result = { result: issue_result.rows }
632 includes = [];
633 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
634 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
635 if (params.include_policies) includes.push({ class: 'policy', objects: 'result' });
636 addRelatedData(conn, req, res, result, includes);
637 });
638 });
639 },
641 '/interest': function (conn, req, res, params) {
642 requireAccessLevel(conn, req, res, 'pseudonym', function() {
643 var query = new selector.Selector();
644 query.from('interest JOIN member ON member.id = interest.member_id JOIN issue on interest.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');
645 query.addField('interest.*');
646 general_params.addMemberOptions(req, query, params);
647 general_params.addIssueOptions(req, query, params);
648 query.addOrderBy('interest.issue_id, interest.member_id');
649 general_params.addLimitAndOffset(query, params);
650 db.query(conn, req, res, query, function (interest_result, conn) {
651 var result = { result: interest_result.rows }
652 includes = [];
653 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
654 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
655 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
656 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
657 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
658 addRelatedData(conn, req, res, result, includes);
659 });
660 });
661 },
663 '/issue_comment': function (conn, req, res, params) {
664 requireAccessLevel(conn, req, res, 'pseudonym', function() {
665 var query = new selector.Selector();
666 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');
667 query.addField('issue_comment.*');
668 general_params.addMemberOptions(req, query, params);
669 general_params.addIssueOptions(req, query, params);
670 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
671 general_params.addLimitAndOffset(query, params);
672 db.query(conn, req, res, query, function (issue_comment_result, conn) {
673 var result = { result: issue_comment_result.rows }
674 includes = [];
675 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
676 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
677 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
678 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
679 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
680 addRelatedData(conn, req, res, result, includes);
681 });
682 });
683 },
685 '/initiative': function (conn, req, res, params) {
686 requireAccessLevel(conn, req, res, 'anonymous', function() {
687 var query = new selector.Selector();
688 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');
689 fields.addObjectFields(query, 'initiative');
690 general_params.addInitiativeOptions(req, query, params);
691 query.addOrderBy('initiative.issue_id, initiative.id');
692 general_params.addLimitAndOffset(query, params);
693 db.query(conn, req, res, query, function (initiative_result, conn) {
694 var result = { result: initiative_result.rows }
695 includes = [];
696 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
697 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
698 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
699 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
700 addRelatedData(conn, req, res, result, includes);
701 });
702 });
703 },
705 '/initiator': function (conn, req, res, params) {
706 requireAccessLevel(conn, req, res, 'pseudonym', function() {
707 var fields = ['initiator.initiative_id', 'initiator.member_id'];
708 var query = new selector.Selector();
709 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');
710 query.addWhere('initiator.accepted');
711 fields.forEach( function(field) {
712 query.addField(field, null, ['grouped']);
713 });
714 general_params.addMemberOptions(req, query, params);
715 general_params.addInitiativeOptions(req, query, params);
716 query.addOrderBy('initiator.initiative_id, initiator.member_id');
717 general_params.addLimitAndOffset(query, params);
718 db.query(conn, req, res, query, function (initiator, conn) {
719 var result = { result: initiator.rows }
720 includes = [];
721 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
722 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
723 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
724 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
725 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
726 addRelatedData(conn, req, res, result, includes);
727 });
728 });
729 },
732 '/supporter': function (conn, req, res, params) {
733 requireAccessLevel(conn, req, res, 'pseudonym', function() {
734 var fields = ['supporter.issue_id', 'supporter.initiative_id', 'supporter.member_id', 'supporter.draft_id'];
735 var query = new selector.Selector();
736 query.from('supporter')
737 query.join('member', null, 'member.id = supporter.member_id JOIN initiative ON initiative.id = supporter.initiative_id JOIN issue ON issue.id = initiative.issue_id JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
738 fields.forEach( function(field) {
739 query.addField(field, null, ['grouped']);
740 });
741 general_params.addMemberOptions(req, query, params);
742 general_params.addInitiativeOptions(req, query, params);
743 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
744 general_params.addLimitAndOffset(query, params);
745 db.query(conn, req, res, query, function (supporter, conn) {
746 var result = { result: supporter.rows }
747 includes = [];
748 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
749 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
750 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
751 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
752 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
753 addRelatedData(conn, req, res, result, includes);
754 });
755 });
756 },
758 '/battle': function (conn, req, res, params) {
759 requireAccessLevel(conn, req, res, 'anonymous', function() {
760 var query = new selector.Selector();
761 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');
762 query.addField('battle.*');
763 general_params.addInitiativeOptions(req, query, params);
764 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
765 general_params.addLimitAndOffset(query, params);
766 db.query(conn, req, res, query, function (result, conn) {
767 var result = { result: result.rows }
768 includes = [];
769 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
770 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
771 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
772 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
773 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
774 addRelatedData(conn, req, res, result, includes);
775 });
776 });
777 },
779 '/draft': function (conn, req, res, params) {
780 requireAccessLevel(conn, req, res, 'anonymous', function() {
781 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
782 var query = new selector.Selector();
783 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');
784 fields.forEach( function(field) {
785 query.addField(field, null, ['grouped']);
786 });
787 if (req.current_access_level != 'anonymous' || req.current_member_id) {
788 query.addField('draft.author_id');
789 }
790 if (params.draft_id) {
791 query.addWhere('draft.id = ?', params.draft_id);
792 }
793 if (params.current_draft) {
794 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
795 }
796 general_params.addInitiativeOptions(req, query, params);
797 query.addOrderBy('draft.initiative_id, draft.id');
798 general_params.addLimitAndOffset(query, params);
799 db.query(conn, req, res, query, function (result, conn) {
800 var result = { result: result.rows }
801 includes = [];
802 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
803 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
804 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
805 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
806 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
807 addRelatedData(conn, req, res, result, includes);
808 });
809 });
810 },
812 '/suggestion': function (conn, req, res, params) {
813 requireAccessLevel(conn, req, res, 'anonymous', function() {
814 var query = new selector.Selector();
815 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');
816 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
817 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
818 } else {
819 fields.addObjectFields(query, 'suggestion');
820 }
821 general_params.addSuggestionOptions(req, query, params);
822 query.addOrderBy('suggestion.initiative_id, suggestion.id');
823 general_params.addLimitAndOffset(query, params);
824 db.query(conn, req, res, query, function (result, conn) {
825 var result = { result: result.rows }
826 includes = [];
827 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
828 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
829 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
830 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
831 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
832 addRelatedData(conn, req, res, result, includes);
833 });
834 });
835 },
837 '/opinion': function (conn, req, res, params) {
838 requireAccessLevel(conn, req, res, 'pseudonym', function() {
839 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
840 var query = new selector.Selector();
841 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');
842 fields.forEach( function(field) {
843 query.addField(field, null, ['grouped']);
844 });
845 general_params.addMemberOptions(req, query, params);
846 general_params.addSuggestionOptions(req, query, params);
847 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
848 general_params.addLimitAndOffset(query, params);
849 db.query(conn, req, res, query, function (result, conn) {
850 var result = { result: result.rows }
851 includes = [];
852 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
853 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
854 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
855 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
856 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
857 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
858 addRelatedData(conn, req, res, result, includes);
859 });
860 });
861 },
863 '/delegation': function (conn, req, res, params) {
864 requireAccessLevel(conn, req, res, 'pseudonym', function() {
865 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
866 var query = new selector.Selector();
867 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');
868 fields.forEach( function(field) {
869 query.addField(field, null, ['grouped']);
870 });
871 if (params.direction) {
872 switch (params.direction) {
873 case 'in':
874 query.join('member', null, 'member.id = delegation.trustee_id');
875 break;
876 case 'out':
877 query.join('member', null, 'member.id = delegation.truster_id');
878 break;
879 default:
880 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
881 }
882 } else {
883 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
884 }
885 general_params.addMemberOptions(req, query, params);
886 general_params.addIssueOptions(req, query, params);
887 if (params.scope) {
888 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
889 };
890 query.addOrderBy = ['delegation.id'];
891 general_params.addLimitAndOffset(query, params);
892 db.query(conn, req, res, query, function (result, conn) {
893 respond('json', conn, req, res, 'ok', { result: result.rows });
894 });
895 });
896 },
898 '/vote': function (conn, req, res, params) {
899 requireAccessLevel(conn, req, res, 'pseudonym', function() {
900 var query = new selector.Selector();
901 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');
902 query.addField('vote.*');
903 query.addWhere('issue.closed_at NOTNULL');
904 general_params.addMemberOptions(req, query, params);
905 general_params.addInitiativeOptions(req, query, params);
906 general_params.addLimitAndOffset(query, params);
907 db.query(conn, req, res, query, function (result, conn) {
908 respond('json', conn, req, res, 'ok', { result: result.rows });
909 });
910 });
911 },
913 '/event': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
914 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'];
915 var query = new selector.Selector();
916 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');
917 fields.forEach( function(field) {
918 query.addField(field, null, ['grouped']);
919 });
920 general_params.addMemberOptions(req, query, params);
921 general_params.addInitiativeOptions(req, query, params);
922 query.addOrderBy('event.id');
923 general_params.addLimitAndOffset(query, params);
924 db.query(conn, req, res, query, function (events, conn) {
925 var result = { result: events.rows }
926 includes = [];
927 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
928 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
929 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
930 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
931 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
932 addRelatedData(conn, req, res, result, includes);
933 });
934 }); },
936 // TODO add interfaces for data structure:
937 // event requireAccessLevel(conn, req, res, 'member');
938 // ignored_member requireAccessLevel(conn, req, res, 'member');
939 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
940 // setting requireAccessLevel(conn, req, res, 'member');
942 };
944 // ==========================================================================
945 // POST methods
946 // ==========================================================================
950 exports.post = {
952 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
953 respond('json', conn, req, res, 'ok', { result: params });
954 }); },
956 '/register_test': function (conn, req, res, params) {
957 var understood = params.understood;
958 var member_login = randomString(16);
959 var member_name = params.name;
960 var member_password = randomString(16);
961 var member_notify_email = params.email;
962 var member_notify_email_secret = randomString(24);
963 var api_key_member = randomString(24);
964 var api_key_full = randomString(24);
965 var api_key_pseudonym = randomString(24);
966 var api_key_anonymous = randomString(24);
968 if (understood != 'understood') {
969 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
970 return;
971 }
973 // add member
974 var query = new selector.SQLInsert('member');
975 query.addValues({
976 login: member_login,
977 password: member_password, // TODO hashing of password
978 notify_email_unconfirmed: member_notify_email,
979 notify_email_secret: member_notify_email_secret,
980 name: member_name
981 });
982 query.addReturning('id');
983 db.query(conn, req, res, query, function (result, conn) {
984 var member_id = result.rows[0].id;
986 // add privilege for root unit
987 var query = new selector.SQLInsert('privilege');
988 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
989 db.query(conn, req, res, query, function (result, conn) {
991 var location = params.location;
992 var unit_id;
993 switch(location) {
994 case 'earth':
995 unit_id = 3;
996 break;
997 case 'moon':
998 unit_id = 4;
999 break;
1000 case 'mars':
1001 unit_id = 5;
1002 break;
1005 // add privilege for selected planet
1006 var query = new selector.SQLInsert('privilege');
1007 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1008 db.query(conn, req, res, query, function (result, conn) {
1010 // add application key
1011 var query = new selector.SQLInsert('member_application');
1012 query.addValues({
1013 member_id: member_id,
1014 name: 'member',
1015 comment: 'access_level member',
1016 access_level: 'member',
1017 key: api_key_member
1018 });
1019 query.addReturning('id');
1021 db.query(conn, req, res, query, function (result, conn) {
1023 // send email to user
1024 email.send({
1025 host : config.mail.smtp_host,
1026 port: config.mail.smtp_port,
1027 ssl: config.mail.smtp_ssl,
1028 domain: config.mail.smtp_domain,
1029 authentication: config.mail.smtp_authentication,
1030 username: config.mail.smtp_username,
1031 password: config.mail.smtp_password,
1032 from: config.mail.from,
1033 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1034 to: member_notify_email,
1035 body: "\
1036 Hello " + member_name + ",\n\
1037 \n\
1038 thank you for registering at the public alpha test of the LiquidFeedback\n\
1039 application programming interface. To complete the registration process,\n\
1040 you need to confirm your email address by opening the following URL:\n\
1041 \n\
1042 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1043 \n\
1044 \n\
1045 After you've confirmed your email address, your account will be automatically\n\
1046 activated.\n\
1047 \n\
1048 Your account name is: " + member_name + "\n\
1049 \n\
1050 \n\
1051 You will need the following login and password to register and unregister\n\
1052 applications for your account later. This function is currently not\n\
1053 implemented, but please keep the credentials for future use.\n\
1054 \n\
1055 Account ID: " + member_id + "\n\
1056 Login: " + member_login + "\n\
1057 Password: " + member_password + "\n\
1058 \n\
1059 \n\
1060 To make you able to actually access the API interface, we added the following\n\
1061 application key with full member access privileges to your account:\n\
1062 \n\
1063 API Key: " + api_key_member + "\n\
1064 \n\
1065 \n\
1066 The base address of the public test is: " + config.public_url_path + "\n\
1067 \n\
1068 The programming interface is described in the LiquidFeedback API\n\
1069 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1070 \n\
1071 The current implementation status of lfapi is published at the LiquidFeedback\n\
1072 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1073 \n\
1074 If you have any questions or suggestions, please use our public mailing list\n\
1075 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1076 \n\
1077 For issues regarding your test account, contact us via email at\n\
1078 lqfb-maintainers@public-software-group.org\n\
1079 \n\
1080 \n\
1081 Sincerely,\n\
1082 \n\
1083 Your LiquidFeedback maintainers",
1084 },
1085 function(err, result){
1086 if(err){ console.log(err); }
1087 });
1089 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1090 });
1091 });
1092 });
1093 });
1094 },
1096 /*
1097 '/register': function (conn, req, res, params) {
1098 var invite_key = params.invite_key;
1099 var login = params.login;
1100 var password = params.password;
1101 var name = params.name;
1102 var notify_email = params.notify_email;
1103 if (!invite_key) {
1104 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1105 return;
1106 };
1107 if (!login) {
1108 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1109 return;
1110 };
1111 if (!password) {
1112 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1113 return;
1114 };
1115 if (!name) {
1116 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1117 return;
1118 };
1119 if (!notify_email) {
1120 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1121 return;
1122 };
1123 // check if akey is valid and get member_id for akey
1124 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) {
1125 if (result.rows.length != 1) {
1126 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1127 return;
1128 };
1129 var member_id = result.rows[0].id;
1130 // check if name is available
1131 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1132 if (result.rows.length > 0) {
1133 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1134 return;
1135 };
1136 // check if login is available
1137 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1138 if (result.rows.length > 0) {
1139 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1140 return;
1141 };
1142 var query = { update: 'member', set: { activation: 'now', active: true, } };
1144 });
1145 });
1146 });
1147 },
1148 */
1150 '/session': function (conn, req, res, params) {
1151 var key = params.key;
1152 if (!key) {
1153 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1154 return;
1155 };
1156 var query = new selector.Selector();
1157 query.from('member');
1158 query.join('member_application', null, 'member_application.member_id = member.id');
1159 query.addField('member.id');
1160 query.addWhere(['member.active AND member_application.key = ?', key]);
1161 if (params.interactive) {
1162 query.forUpdateOf('member');
1164 db.query(conn, req, res, query, function (result, conn) {
1165 if (result.rows.length != 1) {
1166 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1167 return;
1168 };
1169 var member_id = result.rows[0].id;
1170 var session_key = randomString(16);
1171 req.sessions[session_key] = member_id;
1172 var query;
1173 if (params.interactive) {
1174 query = new selector.SQLUpdate('member');
1175 query.addWhere(['member.id = ?', member_id]);
1176 query.addValues({ last_activity: 'now' });
1178 db.query(conn, req, res, query, function (result, conn) {
1179 respond('json', conn, req, res, 'ok', { session_key: session_key });
1180 });
1181 });
1182 },
1184 '/member': function (conn, req, res, params) {
1185 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1186 requireAccessLevel(conn, req, res, 'member', function() {
1187 var query = new selector.SQLUpdate('member');
1188 query.addWhere(['member.id = ?', req.current_member_id]);
1189 fields.forEach( function(field) {
1190 if (typeof(params[field]) != 'undefined') {
1191 query.addValues({ field: params[field] });
1192 } else {
1193 query.addValues({ field: null });
1195 });
1196 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1197 });
1198 },
1200 '/membership': function (conn, req, res, params) {
1201 requireAccessLevel(conn, req, res, 'member', function() {
1203 // check if area_id is set
1204 var area_id = parseInt(params.area_id);
1205 if (!area_id) {
1206 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1207 return;
1210 // delete membership
1211 if (params.delete) {
1212 var query;
1213 query = new selector.SQLDelete('membership');
1214 query.addWhere(['area_id = ?', area_id]);
1215 query.addWhere(['member_id = ?', req.current_member_id]);
1216 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1218 // add membership
1219 } else {
1221 // lock member for upsert
1222 lockMemberById(conn, req, res, req.current_member_id, function() {
1224 // check and lock privilege
1225 requireAreaPrivilege(conn, req, res, area_id, function() {
1227 // upsert membership
1228 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1229 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1230 db.query(conn, req, res, query, function(result) {
1231 respond('json', conn, req, res, 'ok');
1232 });
1233 });
1234 });
1236 });
1237 },
1239 '/interest': function (conn, req, res, params) {
1240 requireAccessLevel(conn, req, res, 'member', function() {
1241 var query;
1243 // check if issue_id is set
1244 var issue_id = parseInt(params.issue_id);
1245 if (!issue_id) {
1246 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1247 return;
1250 // lock member for upsert
1251 lockMemberById(conn, req, res, req.current_member_id, function() {
1253 // delete interest
1254 if (params.delete) {
1256 // check issue state
1257 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1259 // delete interest
1260 query = new selector.SQLDelete('interest');
1261 query.addWhere(['issue_id = ?', issue_id]);
1262 query.addWhere(['member_id = ?', req.current_member_id]);
1263 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1264 });
1266 // add interest
1267 } else {
1269 // check and lock privilege
1270 requireIssuePrivilege(conn, req, res, issue_id, function() {
1272 // check issue state
1273 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1275 // upsert interest
1276 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1277 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1278 db.query(conn, req, res, query, function(result) {
1279 respond('json', conn, req, res, 'ok');
1280 });
1281 });
1282 });
1283 };
1284 });
1285 });
1286 },
1288 '/issue_comment': function (conn, req, res, params) {
1289 requireAccessLevel(conn, req, res, 'member', function() {
1291 var issue_id = parseInt(params.issue_id);
1292 var formatting_engine = params.formatting_engine
1293 var content = params.content;
1295 if (!issue_id) {
1296 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1297 return;
1300 // delete issue comment
1301 if (params.delete) {
1302 var query;
1303 query = new selector.SQLDelete('issue_comment');
1304 query.addWhere(['issue_id = ?', params.issue_id]);
1305 query.addWhere(['member_id = ?', req.current_member_id]);
1306 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1308 // upsert issue comment
1309 } else {
1311 // check if formatting engine is supplied and valid
1312 if (!formatting_engine) {
1313 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1314 return;
1315 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1316 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1317 return;
1318 };
1320 // check if content is supplied
1321 if (!content) {
1322 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1323 return;
1326 // lock member for upsert
1327 lockMemberById(conn, req, res, req.current_member_id, function() {
1329 // check and lock privilege
1330 requireIssuePrivilege(conn, req, res, issue_id, function() {
1332 // upsert issue comment
1333 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1334 query.addValues({
1335 issue_id: issue_id,
1336 member_id: req.current_member_id,
1337 changed: 'now',
1338 formatting_engine: formatting_engine,
1339 content: content
1340 });
1342 db.query(conn, req, res, query, function(result) {
1343 respond('json', conn, req, res, 'ok');
1344 });
1346 });
1347 });
1351 });
1352 },
1354 '/voting_comment': function (conn, req, res, params) {
1355 requireAccessLevel(conn, req, res, 'member', function() {
1357 var issue_id = parseInt(params.issue_id);
1358 var formatting_engine = params.formatting_engine
1359 var content = params.content;
1361 if (!issue_id) {
1362 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1363 return;
1367 // delete voting comment
1368 if (params.delete) {
1369 var query;
1370 query = new selector.SQLDelete('voting_comment');
1371 query.addWhere(['issue_id = ?', params.issue_id]);
1372 query.addWhere(['member_id = ?', req.current_member_id]);
1373 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1375 // upsert voting comment
1376 } else {
1378 // check if formatting engine is supplied and valid
1379 if (!formatting_engine) {
1380 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1381 return;
1382 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1383 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1384 return;
1385 };
1387 // check if content is supplied
1388 if (!content) {
1389 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1390 return;
1393 // lock member for upsert
1394 lockMemberById(conn, req, res, req.current_member_id, function() {
1396 // check and lock privilege
1397 requireIssuePrivilege(conn, req, res, issue_id, function() {
1399 // check issue state
1400 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1402 // upsert voting comment
1403 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1404 query.addValues({
1405 issue_id: issue_id,
1406 member_id: req.current_member_id,
1407 changed: 'now',
1408 formatting_engine: formatting_engine,
1409 content: content
1410 });
1412 db.query(conn, req, res, query, function(result) {
1413 respond('json', conn, req, res, 'ok');
1414 });
1416 });
1417 });
1418 })
1419 };
1420 });
1421 },
1423 '/supporter': function (conn, req, res, params) {
1424 requireAccessLevel(conn, req, res, 'member', function() {
1425 var initiative_id = parseInt(params.initiative_id);
1426 var draft_id = parseInt(params.draft_id);
1428 // check if needed arguments are supplied
1429 if (!initiative_id) {
1430 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1431 return;
1434 if (!draft_id) {
1435 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1436 return;
1439 // lock member for upsert
1440 lockMemberById(conn, req, res, req.current_member_id, function() {
1442 // delete supporter
1443 if (params.delete) {
1445 // check issue state
1446 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1448 // delete supporter
1449 var query = new selector.SQLDelete('supporter');
1450 query.addWhere(['initiative_id = ?', initiative_id]);
1451 query.addWhere(['member_id = ?', req.current_member_id]);
1452 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1454 });
1456 // upsert supporter
1457 } else {
1459 // check and lock privilege
1460 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1462 // check issue state
1463 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1465 // check if given draft is the current one
1466 var query = new selector.Selector('current_draft');
1467 query.addField('NULL');
1468 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1469 query.addWhere(['current_draft.id = ?', draft_id]);
1471 db.query(conn, req, res, query, function(result) {
1472 if (result.rows.length != 1) {
1473 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1474 return;
1477 // upsert supporter
1478 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1479 query.addValues({
1480 initiative_id: initiative_id,
1481 member_id: req.current_member_id,
1482 draft_id: draft_id
1483 });
1485 db.query(conn, req, res, query, function(result) {
1486 respond('json', conn, req, res, 'ok');
1487 });
1489 });
1490 });
1491 });
1492 };
1493 });
1494 });
1495 },
1497 '/draft': function (conn, req, res, params) {
1498 requireAccessLevel(conn, req, res, 'member', function() {
1499 var area_id = parseInt(params.area_id);
1500 var policy_id = parseInt(params.policy_id);
1501 var issue_id = parseInt(params.issue_id);
1502 var initiative_id = parseInt(params.initiative_id);
1503 var initiative_name = params.initiative_name;
1504 var initiative_discussion_url = params.initiative_discussion_url;
1505 var formatting_engine = params.formatting_engine;
1506 var content = params.content;
1508 if (!initiative_discussion_url) initiative_discussion_url = null;
1510 // check parameters
1511 if (!formatting_engine) {
1512 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1513 return;
1514 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1515 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1516 return;
1517 };
1519 if (!content) {
1520 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1521 return;
1522 };
1524 lockMemberById(conn, req, res, req.current_member_id, function() {
1526 // new draft in new initiative in new issue
1527 if (area_id && !issue_id && !initiative_id) {
1529 // check parameters for new issue
1530 if (!policy_id) {
1531 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1532 return;
1535 if (!initiative_name) {
1536 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1537 return;
1540 requireAreaPrivilege(conn, req, res, area_id, function() {
1542 // check if policy is allowed in this area and if area and policy are active
1543 var query = new selector.Selector();
1544 query.from('allowed_policy');
1545 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1546 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1547 query.addField('NULL');
1548 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1549 db.query(conn, req, res, query, function (result, conn) {
1550 if (result.rows.length != 1) {
1551 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.');
1552 return;
1553 };
1555 // check contingent
1556 requireContingentLeft(conn, req, res, true, function() {
1558 // insert new issue
1559 var query = new selector.SQLInsert('issue');
1560 query.addValues({
1561 area_id: area_id,
1562 policy_id: policy_id
1563 });
1564 query.addReturning('id');
1565 db.query(conn, req, res, query, function(result) {
1566 var issue_id = result.rows[0].id;
1568 // insert new initiative
1569 var query = new selector.SQLInsert('initiative');
1570 query.addValues({
1571 issue_id: issue_id,
1572 name: initiative_name,
1573 discussion_url: initiative_discussion_url
1574 });
1575 query.addReturning('id');
1576 db.query(conn, req, res, query, function(result) {
1577 var initiative_id = result.rows[0].id;
1579 // insert initiator
1580 var query = new selector.SQLInsert('initiator');
1581 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1582 db.query(conn, req, res, query, function(result) {
1584 // insert new draft
1585 var query = new selector.SQLInsert('draft');
1586 query.addValues({
1587 initiative_id: initiative_id,
1588 author_id: req.current_member_id,
1589 formatting_engine: formatting_engine,
1590 content: content
1591 });
1592 query.addReturning('id');
1593 db.query(conn, req, res, query, function (result, conn) {
1594 var draft_id = result.rows[0].id;
1596 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1597 });
1598 });
1599 });
1600 });
1601 });
1602 });
1603 });
1605 // new draft in new initiative in existant issue
1606 } else if (issue_id && !area_id && !initiative_id) {
1608 // check privilege
1609 requireIssuePrivilege(conn, req, res, issue_id, function() {
1611 // check issue state
1612 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1614 // check contingent
1615 requireContingentLeft(conn, req, res, true, function() {
1617 // insert initiative
1618 var query = new selector.SQLInsert('initiative');
1619 query.addValues({
1620 issue_id: issue_id,
1621 name: initiative_name,
1622 discussion_url: initiative_discussion_url
1623 });
1624 query.addReturning('id');
1625 db.query(conn, req, res, query, function(result) {
1626 var initiative_id = result.rows[0].id;
1628 // insert initiator
1629 var query = new selector.SQLInsert('initiator');
1630 query.addValues({
1631 initiative_id: initiative_id,
1632 member_id: req.current_member_id,
1633 accepted: true
1634 });
1635 db.query(conn, req, res, query, function(result) {
1637 // insert draft
1638 var query = new selector.SQLInsert('draft');
1639 query.addValues({
1640 initiative_id: initiative_id,
1641 author_id: req.current_member_id,
1642 formatting_engine: formatting_engine,
1643 content: content
1644 });
1645 query.addReturning('id');
1646 db.query(conn, req, res, query, function (result, conn) {
1648 var draft_id = result.rows[0].id;
1649 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1651 });
1652 });
1653 });
1654 });
1655 });
1656 });
1658 // new draft in existant initiative
1659 } else if (initiative_id && !area_id && !issue_id ) {
1661 // check privilege
1662 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1664 // check issue state
1665 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1668 // get initiator
1669 var query = new selector.Selector();
1670 query.from('initiator');
1671 query.addField('accepted');
1672 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1673 db.query(conn, req, res, query, function (result, conn) {
1675 // if member is not initiator, deny creating new draft
1676 if (result.rows.length != 1) {
1677 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1678 return;
1680 var initiator = result.rows[0];
1681 if (!initiator.accepted) {
1682 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.');
1683 return;
1684 };
1686 // check contingent
1687 requireContingentLeft(conn, req, res, false, function() {
1689 // insert new draft
1690 var query = new selector.SQLInsert('draft');
1691 query.addValues({
1692 initiative_id: initiative_id,
1693 author_id: req.current_member_id,
1694 formatting_engine: formatting_engine,
1695 content: content
1696 });
1697 query.addReturning('id');
1698 db.query(conn, req, res, query, function (result, conn) {
1700 var draft_id = result.rows[0].id;
1701 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1702 });
1703 });
1704 });
1705 });
1706 });
1708 // none of them (invalid request)
1709 } else {
1710 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1711 };
1713 });
1714 });
1715 },
1717 '/suggestion': function (conn, req, res, params) {
1718 requireAccessLevel(conn, req, res, 'member', function() {
1719 // TODO
1720 });
1721 },
1723 '/opinion': function (conn, req, res, params) {
1724 requireAccessLevel(conn, req, res, 'member', function() {
1725 // TODO
1726 });
1727 },
1729 '/delegation': function (conn, req, res, params) {
1730 requireAccessLevel(conn, req, res, 'member', function() {
1731 var unit_id = parseInt(params.unit_id);
1732 var area_id = parseInt(params.area_id);
1733 var issue_id = parseInt(params.issue_id);
1734 var trustee_id;
1736 if (params.trustee_id == '') {
1737 trustee_id = null;
1738 } else {
1739 trustee_id = parseInt(params.trustee_id);
1742 lockMemberById(conn, req, res, req.current_member_id, function() {
1744 if (params.delete) {
1745 var query = new selector.SQLDelete('delegation')
1746 if (unit_id && !area_id && !issue_id) {
1747 query.addWhere(['unit_id = ?', unit_id]);
1748 } else if (!unit_id && area_id && !issue_id) {
1749 query.addWhere(['area_id = ?', area_id]);
1750 } else if (!unit_id && !area_id && issue_id) {
1751 query.addWhere(['issue_id = ?', issue_id]);
1752 } else {
1753 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1754 return;
1756 query.addWhere(['truster_id = ?', req.current_member_id]);
1757 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1758 } else {
1759 var query = new selector.Upserter('delegation', ['truster_id']);
1760 query.addValues({
1761 truster_id: req.current_member_id,
1762 trustee_id: trustee_id
1763 });
1764 if (unit_id && !area_id && !issue_id) {
1766 // check privilege
1767 requireUnitPrivilege(conn, req, res, unit_id, function() {
1769 query.addKeys(['unit_id'])
1770 query.addValues({ unit_id: unit_id, scope: 'unit' });
1771 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1772 });
1774 } else if (!unit_id && area_id && !issue_id) {
1776 // check privilege
1777 requireAreaPrivilege(conn, req, res, area_id, function() {
1779 query.addKeys(['area_id'])
1780 query.addValues({ area_id: area_id, scope: 'area' });
1781 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1782 });
1784 } else if (!unit_id && !area_id && issue_id) {
1786 // check privilege
1787 requireIssuePrivilege(conn, req, res, issue_id, function() {
1789 // check issue state
1790 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1792 query.addKeys(['issue_id'])
1793 query.addValues({ issue_id: issue_id, scope: 'issue' });
1794 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1795 });
1796 });
1797 } else {
1798 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1799 return;
1803 });
1805 });
1806 },
1808 };

Impressum / About Us