lfapi

view lfapi/main.js @ 6:cf05d9428ecc

Do not set Content-Length for json responses. Needs to be fixed again.
author bsw
date Mon Oct 24 20:57:38 2011 +0200 (2011-10-24)
parents ef8aff2e0f67
children 3b08c5ed16e6
line source
1 var api_version = '0.2.0';
3 // creates a random string with the given length
4 function randomString(number_of_chars) {
5 var charset, rand, i, ret;
6 charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7 random_string = '';
9 for (var i = 0; i < number_of_chars; i++) {
10 random_string += charset[parseInt(Math.random() * charset.length)]
11 }
12 return random_string;
13 }
15 var fields = require('./fields.js');
17 var general_params = require('./general_params.js');
19 var config = general_params.config;
20 exports.config = config;
22 var db = require('./db.js');
23 exports.db = db;
25 var selector = db.selector;
27 var nodemailer = require('nodemailer');
30 // check if current session has at least given access level, returns error to client if not.
31 // used by request handlers below
32 function requireAccessLevel(conn, req, res, access_level, callback) {
33 switch (access_level) {
34 case 'anonymous':
35 if (req.current_access_level == 'anonymous') { callback(); return; };
36 case 'pseudonym':
37 if (req.current_access_level == 'pseudonym') { callback(); return; };
38 case 'full':
39 if (req.current_access_level == 'full') { callback(); return; };
40 case 'member':
41 if (req.current_member_id) { callback(); return; };
42 default:
43 respond('json', conn, req, res, 'forbidden', { error: 'Access denied' });
44 }
45 };
47 // callback function, encoding result and sending it to the client
48 function respond(mode, conn, req, res, status, object, err) {
49 var http_status = 200;
50 var command;
52 if (status == 'ok') {
53 command = 'COMMIT';
54 } else {
55 command = 'ROLLBACK';
56 };
58 switch (status) {
59 case 'ok':
60 http_status = 200;
61 break;
62 case 'forbidden':
63 //http_status = 403;
64 break;
65 case 'notfound':
66 http_status = 404;
67 break;
68 case 'unprocessable':
69 //http_status = 422;
70 break;
71 case 'conflict':
72 //http_status = 409;
73 break;
74 };
76 var query;
77 if (mode == 'json' && ! err) query = 'SELECT null';
78 db.query(conn, req, res, query, function(result, conn) {
79 db.query(conn, req, res, command, function (result, conn) {
81 if (mode == 'json') {
82 if (! object) object = {};
83 } else if (mode == 'html') {
84 if (! object) object = 'no content';
85 if (err) object = "Error: " + err;
86 }
88 object.status = status;
89 object.error = err;
91 if (mode == 'json') {
92 var body = JSON.stringify(object);
93 var content_type = 'application/json';
94 if (req.params && req.params.callback) {
95 body = req.params.callback + '(' + body + ')';
96 content_type = 'text/javascript';
97 }
98 res.writeHead(
99 http_status,
100 {
101 'Content-Type': content_type,
102 //'Content-Length': body.length // TODO doesn't work in chrome with JSONP
103 }
104 );
105 res.end(body);
106 } else if (mode == 'html') {
107 var body = ['<html><head><title>lfapi</title><style>body { font-family: sans-serif; }</style></head><body>']
108 body.push(object)
109 body.push('</body></html>')
110 body = body.join('');
111 res.writeHead(
112 http_status,
113 {
114 'Content-Type': 'text/html',
115 'Content-Length': body.length
116 }
117 );
118 res.end(body);
119 }
120 })
121 });
122 };
124 exports.respond = respond;
125 db.error_handler = respond;
127 // add requested related data for requests with include_* parameters
128 function addRelatedData(conn, req, res, result, includes) {
129 if (includes.length > 0) {
130 var include = includes.shift();
131 var class = include.class;
132 var objects = result[include.objects];
134 var query;
136 if (objects) {
137 var objects_exists = false;
138 var ids_hash = {};
139 if (typeof(objects) == 'array') {
140 if (objects.length > 0) {
141 objects_exists = true;
142 objects.forEach( function(object) {
143 if (object[class + "_id"]) {
144 ids_hash[object[class + "_id"]] = true;
145 };
146 });
147 }
148 } else {
149 for (var key in objects) {
150 objects_exists = true;
151 var object = objects[key];
152 if (object[class + "_id"]) {
153 ids_hash[object[class + "_id"]] = true;
154 };
155 };
156 };
158 if (objects_exists) {
159 var ids = [];
160 for (key in ids_hash) {
161 ids.push(key)
162 }
163 if (ids.length > 0) {
164 query = new selector.Selector();
165 query.from(class);
166 query.addWhere([class + '.id IN (??)', ids]);
167 fields.addObjectFields(query, class);
168 }
169 };
170 };
172 db.query(conn, req, res, query, function (result2, conn) {
173 // add result to main result, regarding correct pluralization
174 var tmp = {};
175 if (result2) {
176 result2.rows.forEach( function(row) {
177 tmp[row.id] = row;
178 });
179 };
181 if (class == 'policy') {
182 result['policies'] = tmp;
183 } else {
184 result[class + 's'] = tmp;
185 }
186 addRelatedData(conn, req, res, result, includes);
187 });
188 } else {
189 respond('json', conn, req, res, 'ok', result);
190 };
192 };
194 function lockMemberById(conn, req, res, member_id, callback) {
195 var query = new selector.Selector('member');
196 query.addField('NULL');
197 query.addWhere(['member.id = ?', member_id]);
198 query.forUpdate();
199 db.query(conn, req, res, query, callback);
200 };
202 function requireUnitPrivilege(conn, req, res, unit_id, callback) {
203 var query = new selector.Selector('privilege');
204 query.addField('NULL');
205 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
206 query.addWhere(['privilege.unit_id = ?', unit_id ]);
207 query.addWhere('privilege.voting_right');
208 query.forShareOf('privilege');
209 db.query(conn, req, res, query, function(result, conn) {
210 if (result.rows.length != 1) {
211 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for this unit.');
212 return;
213 }
214 callback();
215 });
216 };
218 function requireAreaPrivilege(conn, req, res, area_id, callback) {
219 var query = new selector.Selector('privilege');
220 query.join('area', null, 'area.unit_id = privilege.unit_id');
221 query.addField('NULL');
222 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
223 query.addWhere(['area.id = ?', area_id ]);
224 query.addWhere('privilege.voting_right');
225 query.forShareOf('privilege');
226 db.query(conn, req, res, query, function(result, conn) {
227 if (result.rows.length != 1) {
228 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for areas in this unit.');
229 return;
230 }
231 callback();
232 });
233 };
235 function requireIssuePrivilege(conn, req, res, issue_id, callback) {
236 var query = new selector.Selector('privilege');
237 query.join('area', null, 'area.unit_id = privilege.unit_id');
238 query.join('issue', null, 'issue.area_id = area.id');
239 query.addField('NULL');
240 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
241 query.addWhere(['issue.id = ?', issue_id ]);
242 query.addWhere('privilege.voting_right');
243 query.forShareOf('privilege');
244 db.query(conn, req, res, query, function(result, conn) {
245 if (result.rows.length != 1) {
246 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for issues in this unit.');
247 return;
248 }
249 callback();
250 });
251 };
253 function requireInitiativePrivilege(conn, req, res, initiative_id, callback) {
254 var query = new selector.Selector('privilege');
255 query.join('area', null, 'area.unit_id = privilege.unit_id');
256 query.join('issue', null, 'issue.area_id = area.id');
257 query.join('initiative', null, 'initiative.issue_id = issue.id');
258 query.addField('NULL');
259 query.addWhere(['privilege.member_id = ?', req.current_member_id]);
260 query.addWhere(['initiative.id = ?', initiative_id ]);
261 query.addWhere('privilege.voting_right');
262 query.forShareOf('privilege');
263 db.query(conn, req, res, query, function(result, conn) {
264 if (result.rows.length != 1) {
265 respond('json', conn, req, res, 'forbidden', null, 'You have no voting right for initiatives in this unit.');
266 return;
267 }
268 callback();
269 });
270 };
272 function requireIssueState(conn, req, res, issue_id, required_states, callback) {
273 var query = new selector.Selector('issue');
274 query.addField('NULL');
275 query.addWhere(['issue.id = ?', issue_id]);
276 query.addWhere(['issue.state IN (??)', required_states]);
277 query.forUpdateOf('issue');
278 db.query(conn, req, res, query, function(result, conn) {
279 if (result.rows.length != 1) {
280 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
281 return;
282 }
283 callback();
284 });
285 };
287 function requireIssueStateForInitiative(conn, req, res, initiative_id, required_states, callback) {
288 var query = new selector.Selector('issue');
289 query.join('initiative', null, 'initiative.issue_id = issue.id');
290 query.addField('NULL');
291 query.addWhere(['initiative.id = ?', initiative_id]);
292 query.addWhere(['issue.state IN (??)', required_states]);
293 query.forUpdateOf('issue');
294 db.query(conn, req, res, query, function(result, conn) {
295 if (result.rows.length != 1) {
296 respond('json', conn, req, res, 'forbidden', null, 'Issue is in wrong state.');
297 return;
298 }
299 callback();
300 });
301 }
303 function requireContingentLeft(conn, req, res, is_initiative, callback) {
304 var query = new selector.Selector('member_contingent_left');
305 query.addField('NULL');
306 query.addWhere(['member_contingent_left.member_id = ?', req.current_member_id]);
307 query.addWhere('member_contingent_left.text_entries_left >= 1');
308 if (is_initiative) {
309 query.addWhere('member_contingent_left.initiatives_left >= 1');
310 }
311 db.query(conn, req, res, query, function(result, conn) {
312 if (result.rows.length != 1) {
313 respond('json', conn, req, res, 'forbidden', null, 'Contingent empty.');
314 return;
315 }
316 callback();
317 });
318 }
320 // ==========================================================================
321 // GETT methods
322 // ==========================================================================
325 exports.get = {
327 // startpage (html) for users
328 // currently used for implementing public alpha test
329 '/': function (conn, req, res, params) {
331 var html = [];
332 html.push('<h2>welcome to lfapi public developer alpha test</h2>');
333 html.push('<p>This service is provided for testing purposes and is <i><b>dedicated to developers interested in creating applications</b></i> based on LiquidFeedback.</p>');
334 html.push('<h2>how to use</h2>');
335 html.push('<p>The programming interface is described in the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/API">LiquidFeedback API specification</a>.</p>')
336 html.push('<p>The current implementation status of lfapi is published at the <a href="http://dev.liquidfeedback.org/trac/lf/wiki/lfapi">LiquidFeedback API server</a> page in our Wiki.</p>');
337 html.push('<p><b><i>Neither the API specification nor the implementation of lfapi is finished yet.</i></b> This public test should enable developers to join the specification process of the programming interface and makes it possible to start creating applications.</p>');
338 html.push('<h2>questions and suggestions</h2>');
339 html.push('<p>Please use our <a href="http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main">public mailing list</a> if you have any questions or suggestions.</p>');
340 html.push('<h2>developer registration</h2>');
341 html.push('<p>To register as developer and receive an account, please submit the following form. You\'ll receive an email with instructions to complete the registration process by verifying your email address.<br />');
342 html.push('<form action="register_test" method="POST">');
343 html.push('<label for="name">Your name:</label> <input type="text" id="name" name="name" /> &nbsp; &nbsp; ');
344 html.push('<label for="email">Email address:</label> <input type="text" id="email" name="email" /> &nbsp; &nbsp; ');
345 html.push('<label for="location">Location:</label> <select name="location" id="location"><option value="earth">Earth</option><option value="moon">Moon</option><option value="mars">Mars</option></select>');
346 html.push('<br />');
347 html.push('<br />');
348 html.push('<div style="border: 2px solid #c00000; background-color: #ffa0a0; padding: 1ex;">');
349 html.push('<b>WARNING:</b> All data you entered above and all data you enter later while using the system and all data you are submitting via the programming interface will be stored in the LiquidFeedback database and published. Every access to the system is subject of tracing and logging for development purposes.<br />Please notice, this is a <b>public alpha test dedicated to developers</b>: serious errors can happen, private data unintentionally published or even <a href="http://en.wikipedia.org/wiki/Grey_goo"> grey goo</a> can appear without further warning. Everything is <b>ON YOUR OWN RISK</b>!');
350 html.push('<br />');
351 html.push('<br />');
352 html.push('<input type="checkbox" name="understood" value="understood" /> I understand the previous warning and I understand that everything is on my own risk.<br />');
353 html.push('</div>');
354 html.push('<br />');
355 html.push('<input type="submit" value="Register account" />');
356 respond('html', null, req, res, 'ok', html.join(''));
357 },
359 // temporary method to implement public alpha test
360 '/register_test_confirm': function (conn, req, res, params) {
361 var secret = params.secret;
363 var query = new selector.Selector('member');
364 query.addField('member.id, member.notify_email_unconfirmed');
365 query.addWhere(['member.notify_email_secret = ?', secret]);
366 db.query(conn, req, res, query, function (result, conn) {
367 var member = result.rows[0];
368 if (member) {
369 var query = new selector.SQLUpdate('member');
370 query.addValues({
371 notify_email: member.notify_email_unconfirmed,
372 notify_email_secret: null,
373 notify_email_unconfirmed: null,
374 active: true,
375 activated: 'now',
376 active: true,
377 last_activity: 'now',
378 locked: false
379 });
380 query.addWhere(['id = ?', member.id]);
381 db.query(conn, req, res, query, function (err, result) {
382 respond('html', conn, req, res, 'ok', 'Account activated: ');
383 });
384 } else {
385 respond('html', conn, req, res, 'forbidden', 'Secret not valid or already used.');
386 }
387 })
388 },
390 '/info': function (conn, req, res, params) {
391 requireAccessLevel(conn, req, res, 'anonymous', function() {
392 var query = new selector.Selector();
393 query.from('"liquid_feedback_version"');
394 query.addField('"liquid_feedback_version".*');
395 db.query(conn, req, res, query, function (result, conn) {
396 var liquid_feedback_version = result.rows[0];
397 var query = new selector.Selector();
398 query.from('"system_setting"');
399 query.addField('"member_ttl"');
400 db.query(conn, req, res, query, function (result, conn) {
401 var member_ttl = null;
402 if (result.rows[0]) {
403 member_ttl = result.rows[0].member_ttl;
404 };
405 respond('json', conn, req, res, 'ok', {
406 core_version: liquid_feedback_version.string,
407 api_version: api_version,
408 current_access_level: req.current_member_id ? 'member' : req.current_access_level,
409 current_member_id: req.current_member_id,
410 member_ttl: member_ttl,
411 settings: config.settings
412 });
413 });
414 });
415 });
416 },
418 '/member_count': function (conn, req, res, params) {
419 requireAccessLevel(conn, req, res, 'anonymous', function() {
420 var query = new selector.Selector();
421 query.from('"member_count"');
422 query.addField('"member_count".*');
423 db.query(conn, req, res, query, function (result, conn) {
424 var member_count = result.rows[0];
425 respond('json', conn, req, res, 'ok', {
426 total_count: member_count.total_count,
427 calculated: member_count.calculated
428 });
429 });
430 });
431 },
433 '/contingent': function (conn, req, res, params) {
434 requireAccessLevel(conn, req, res, 'anonymous', function() {
435 var query = new selector.Selector();
436 query.from('"contingent"');
437 query.addField('"contingent".*');
438 db.query(conn, req, res, query, function (result, conn) {
439 respond('json', conn, req, res, 'ok', { result: result.rows });
440 });
441 });
442 },
444 '/contingent_left': function (conn, req, res, params) {
445 requireAccessLevel(conn, req, res, 'member', function() {
446 var query = new selector.Selector();
447 query.from('"member_contingent_left"');
448 query.addField('"member_contingent_left".text_entries_left');
449 query.addField('"member_contingent_left".initiatives_left');
450 query.addWhere(['member_id = ?', req.current_member_id]);
451 db.query(conn, req, res, query, function (result, conn) {
452 respond('json', conn, req, res, 'ok', { result: result.rows[0] });
453 });
454 });
455 },
457 '/member': function (conn, req, res, params) {
458 requireAccessLevel(conn, req, res, 'pseudonym', function() {
459 var query = new selector.Selector();
460 query.from('"member"');
461 if (req.current_access_level == 'pseudonym' && !req.current_member_id ) {
462 fields.addObjectFields(query, 'member', 'member_pseudonym');
463 } else {
464 fields.addObjectFields(query, 'member');
465 }
466 general_params.addMemberOptions(req, query, params);
467 query.addOrderBy('"member"."id"');
468 general_params.addLimitAndOffset(query, params);
469 db.query(conn, req, res, query, function (result, conn) {
470 respond('json', conn, req, res, 'ok', { result: result.rows });
471 });
472 });
473 },
475 '/member_history': function (conn, req, res, params) {
476 requireAccessLevel(conn, req, res, 'full', function() {
477 var query = new selector.Selector();
478 query.from('"member_history" JOIN "member" ON "member"."id" = "member_history"."member_id"');
479 query.addField('"member_history".*');
480 general_params.addMemberOptions(req, query, params);
481 query.addOrderBy('member_history.id');
482 general_params.addLimitAndOffset(query, params);
483 db.query(conn, req, res, query, function (member_history_result, conn) {
484 var result = { result: member_history_result.rows }
485 includes = [];
486 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
487 addRelatedData(conn, req, res, result, includes);
488 });
489 });
490 },
492 '/member_image': function (conn, req, res, params) {
493 requireAccessLevel(conn, req, res, 'full', function() {
494 var query = new selector.Selector();
495 query.from('"member_image" JOIN "member" ON "member"."id" = "member_image"."member_id"');
496 query.addField('"member_image".*');
497 query.addWhere('member_image.scaled');
498 general_params.addMemberOptions(req, query, params);
499 query.addOrderBy = ['member_image.member_id, member_image.image_type'];
500 db.query(conn, req, res, query, function (result, conn) {
501 respond('json', conn, req, res, 'ok', { result: result.rows });
502 });
503 });
504 },
506 '/contact': function (conn, req, res, params) {
507 requireAccessLevel(conn, req, res, 'pseudonym', function() {
508 var query = new selector.Selector();
509 query.from('contact JOIN member ON member.id = contact.member_id');
510 query.addField('"contact".*');
511 if (req.current_member_id) {
512 // public or own for members
513 query.addWhere(['"contact"."public" OR "contact"."member_id" = ?', req.current_member_id]);
514 } else {
515 // public for everybody
516 query.addWhere('"contact"."public"');
517 }
518 general_params.addMemberOptions(req, query, params);
519 query.addOrderBy('"contact"."id"');
520 general_params.addLimitAndOffset(query, params);
521 db.query(conn, req, res, query, function (result, conn) {
522 respond('json', conn, req, res, 'ok', { result: result.rows });
523 });
524 });
525 },
527 '/privilege': function (conn, req, res, params) {
528 requireAccessLevel(conn, req, res, 'pseudonym', function() {
529 var query = new selector.Selector();
530 query.from('privilege JOIN member ON member.id = privilege.member_id JOIN unit ON unit.id = privilege.unit_id');
531 query.addField('privilege.*');
532 general_params.addUnitOptions(req, query, params);
533 general_params.addMemberOptions(req, query, params);
534 query.addOrderBy('privilege.unit_id, privilege.member_id');
535 general_params.addLimitAndOffset(query, params);
536 db.query(conn, req, res, query, function (privilege_result, conn) {
537 var result = { result: privilege_result.rows }
538 includes = [];
539 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
540 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
541 addRelatedData(conn, req, res, result, includes);
542 });
543 });
544 },
546 '/policy': function (conn, req, res, params) {
547 requireAccessLevel(conn, req, res, 'anonymous', function() {
548 var query = new selector.Selector();
549 query.from('"policy"');
550 query.addField('"policy".*');
551 general_params.addPolicyOptions(req, query, params);
552 query.addOrderBy('"policy"."index"');
553 general_params.addLimitAndOffset(query, params);
554 db.query(conn, req, res, query, function (result, conn) {
555 respond('json', conn, req, res, 'ok', { result: result.rows });
556 });
557 });
558 },
560 '/unit': function (conn, req, res, params) {
561 requireAccessLevel(conn, req, res, 'anonymous', function() {
562 var query = new selector.Selector();
563 query.from('"unit"');
564 fields.addObjectFields(query, 'unit');
565 general_params.addUnitOptions(req, query, params);
566 query.addOrderBy('unit.id');
567 general_params.addLimitAndOffset(query, params);
568 db.query(conn, req, res, query, function (result, conn) {
569 respond('json', conn, req, res, 'ok', { result: result.rows });
570 });
571 });
572 },
574 '/area': function (conn, req, res, params) {
575 requireAccessLevel(conn, req, res, 'anonymous', function() {
576 var query = new selector.Selector();
577 query.from('area JOIN unit ON area.unit_id = unit.id');
578 fields.addObjectFields(query, 'area');
579 general_params.addAreaOptions(req, query, params);
580 query.addOrderBy('area.id');
581 general_params.addLimitAndOffset(query, params);
582 db.query(conn, req, res, query, function (area_result, conn) {
583 var result = { result: area_result.rows }
584 includes = [];
585 if (params.include_units) includes.push({ class: 'unit', objects: 'result'});
586 addRelatedData(conn, req, res, result, includes);
587 });
588 });
589 },
591 '/allowed_policy': function (conn, req, res, params) {
592 requireAccessLevel(conn, req, res, 'anonymous', function() {
593 var query = new selector.Selector();
594 query.from('allowed_policy');
595 query.join('area', null, 'area.id = allowed_policy.area_id');
596 query.join('unit', null, 'unit.id = area.unit_id');
597 query.addField('allowed_policy.*');
598 general_params.addAreaOptions(req, query, params);
599 query.addOrderBy('allowed_policy.area_id, allowed_policy.policy_id');
600 general_params.addLimitAndOffset(query, params);
601 db.query(conn, req, res, query, function (allowed_policy_result, conn) {
602 var result = { result: allowed_policy_result.rows }
603 includes = [];
604 if (params.include_policies) includes.push({ class: 'policy', objects: 'result'});
605 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
606 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
607 addRelatedData(conn, req, res, result, includes);
608 });
609 }); },
611 '/membership': function (conn, req, res, params) {
612 requireAccessLevel(conn, req, res, 'pseudonym', function() {
613 var query = new selector.Selector();
614 query.from('membership JOIN member ON membership.member_id = member.id JOIN area ON area.id = membership.area_id JOIN unit ON unit.id = area.unit_id');
615 query.addField('membership.*');
616 general_params.addAreaOptions(req, query, params);
617 general_params.addMemberOptions(req, query, params);
618 query.addOrderBy('membership.area_id, membership.member_id');
619 general_params.addLimitAndOffset(query, params);
620 db.query(conn, req, res, query, function (membership_result, conn) {
621 var result = { result: membership_result.rows }
622 includes = [];
623 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
624 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
625 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
626 addRelatedData(conn, req, res, result, includes);
627 });
628 });
629 },
631 '/issue': function (conn, req, res, params) {
632 requireAccessLevel(conn, req, res, 'anonymous', function() {
633 var query = new selector.Selector()
634 query.from('issue JOIN policy ON policy.id = issue.policy_id JOIN area ON area.id = issue.area_id JOIN unit ON area.unit_id = unit.id');
635 fields.addObjectFields(query, 'issue');
636 general_params.addIssueOptions(req, query, params);
637 query.addOrderBy('issue.id');
638 general_params.addLimitAndOffset(query, params);
639 db.query(conn, req, res, query, function (issue_result, conn) {
640 var result = { result: issue_result.rows }
641 includes = [];
642 if (params.include_areas) includes.push({ class: 'area', objects: 'result'});
643 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
644 if (params.include_policies) includes.push({ class: 'policy', objects: 'result' });
645 addRelatedData(conn, req, res, result, includes);
646 });
647 });
648 },
650 '/interest': function (conn, req, res, params) {
651 requireAccessLevel(conn, req, res, 'pseudonym', function() {
652 var query = new selector.Selector();
653 if (!params.snapshot) {
654 query.from('interest');
655 } else if (params.snapshot == 'latest') {
656 query.from('direct_interest_snapshot', 'interest');
657 query.addWhere('interest.event = issue.latest_snapshot_event');
658 };
659 query.addField('interest.*');
660 query.join('member', null, 'member.id = interest.member_id');
661 query.join('issue', null, 'interest.issue_id = issue.id');
662 query.join('policy', null, 'policy.id = issue.policy_id');
663 query.join('area', null, 'area.id = issue.area_id');
664 query.join('unit', null, 'area.unit_id = unit.id');
665 general_params.addMemberOptions(req, query, params);
666 general_params.addIssueOptions(req, query, params);
667 query.addOrderBy('interest.issue_id, interest.member_id');
668 general_params.addLimitAndOffset(query, params);
669 db.query(conn, req, res, query, function (interest_result, conn) {
670 var result = { result: interest_result.rows }
671 includes = [];
672 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
673 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
674 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
675 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
676 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
677 addRelatedData(conn, req, res, result, includes);
678 });
679 });
680 },
682 '/issue_comment': function (conn, req, res, params) {
683 requireAccessLevel(conn, req, res, 'pseudonym', function() {
684 var query = new selector.Selector();
685 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');
686 query.addField('issue_comment.*');
687 general_params.addMemberOptions(req, query, params);
688 general_params.addIssueOptions(req, query, params);
689 query.addOrderBy('issue_comment.issue_id, issue_comment.member_id');
690 general_params.addLimitAndOffset(query, params);
691 db.query(conn, req, res, query, function (issue_comment_result, conn) {
692 var result = { result: issue_comment_result.rows }
693 includes = [];
694 if (params.include_members) includes.push({ class: 'member', objects: 'result'});
695 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
696 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
697 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
698 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
699 addRelatedData(conn, req, res, result, includes);
700 });
701 });
702 },
704 '/initiative': function (conn, req, res, params) {
705 requireAccessLevel(conn, req, res, 'anonymous', function() {
706 var query = new selector.Selector();
707 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');
708 fields.addObjectFields(query, 'initiative');
709 query.addOrderBy('initiative.id');
710 general_params.addInitiativeOptions(req, query, params);
711 general_params.addLimitAndOffset(query, params);
712 db.query(conn, req, res, query, function (initiative_result, conn) {
713 var result = { result: initiative_result.rows }
714 includes = [];
715 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
716 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
717 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
718 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
719 addRelatedData(conn, req, res, result, includes);
720 });
721 });
722 },
724 '/initiator': function (conn, req, res, params) {
725 requireAccessLevel(conn, req, res, 'pseudonym', function() {
726 var fields = ['initiator.initiative_id', 'initiator.member_id'];
727 var query = new selector.Selector();
728 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');
729 query.addWhere('initiator.accepted');
730 fields.forEach( function(field) {
731 query.addField(field, null, ['grouped']);
732 });
733 general_params.addMemberOptions(req, query, params);
734 general_params.addInitiativeOptions(req, query, params);
735 query.addOrderBy('initiator.initiative_id, initiator.member_id');
736 general_params.addLimitAndOffset(query, params);
737 db.query(conn, req, res, query, function (initiator, conn) {
738 var result = { result: initiator.rows }
739 includes = [];
740 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
741 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
742 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
743 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
744 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
745 addRelatedData(conn, req, res, result, includes);
746 });
747 });
748 },
751 '/supporter': function (conn, req, res, params) {
752 requireAccessLevel(conn, req, res, 'pseudonym', function() {
753 var fields = ['supporter.issue_id', 'supporter.initiative_id', 'supporter.member_id', 'supporter.draft_id'];
754 var query = new selector.Selector();
755 query.from('supporter')
756 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');
757 fields.forEach( function(field) {
758 query.addField(field, null, ['grouped']);
759 });
760 general_params.addMemberOptions(req, query, params);
761 general_params.addInitiativeOptions(req, query, params);
762 query.addOrderBy('supporter.issue_id, supporter.initiative_id, supporter.member_id');
763 general_params.addLimitAndOffset(query, params);
764 db.query(conn, req, res, query, function (supporter, conn) {
765 var result = { result: supporter.rows }
766 includes = [];
767 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
768 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
769 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
770 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
771 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
772 addRelatedData(conn, req, res, result, includes);
773 });
774 });
775 },
777 '/battle': function (conn, req, res, params) {
778 requireAccessLevel(conn, req, res, 'anonymous', function() {
779 var query = new selector.Selector();
780 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');
781 query.addField('battle.*');
782 general_params.addInitiativeOptions(req, query, params);
783 query.addOrderBy('battle.issue_id, battle.winning_initiative_id, battle.losing_initiative_id');
784 general_params.addLimitAndOffset(query, params);
785 db.query(conn, req, res, query, function (result, conn) {
786 var result = { result: result.rows }
787 includes = [];
788 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
789 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
790 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
791 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
792 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
793 addRelatedData(conn, req, res, result, includes);
794 });
795 });
796 },
798 '/draft': function (conn, req, res, params) {
799 requireAccessLevel(conn, req, res, 'anonymous', function() {
800 var fields = ['draft.initiative_id', 'draft.id', 'draft.formatting_engine', 'draft.content', 'draft.author_id'];
801 var query = new selector.Selector();
802 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');
803 fields.forEach( function(field) {
804 query.addField(field, null, ['grouped']);
805 });
806 if (req.current_access_level != 'anonymous' || req.current_member_id) {
807 query.addField('draft.author_id');
808 }
809 if (params.draft_id) {
810 query.addWhere('draft.id = ?', params.draft_id);
811 }
812 if (params.current_draft) {
813 query.join('current_draft', null, 'current_draft.initiative_id = initiative.id AND current_draft.id = draft.id')
814 }
815 general_params.addInitiativeOptions(req, query, params);
816 query.addOrderBy('draft.initiative_id, draft.id');
817 general_params.addLimitAndOffset(query, params);
818 db.query(conn, req, res, query, function (result, conn) {
819 var result = { result: result.rows }
820 includes = [];
821 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
822 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
823 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
824 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
825 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
826 addRelatedData(conn, req, res, result, includes);
827 });
828 });
829 },
831 '/suggestion': function (conn, req, res, params) {
832 requireAccessLevel(conn, req, res, 'anonymous', function() {
833 var query = new selector.Selector();
834 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');
835 if (req.current_access_level == 'anonymous' && !req.current_member_id ) {
836 fields.addObjectFields(query, 'suggestion', 'suggestion_pseudonym');
837 } else {
838 fields.addObjectFields(query, 'suggestion');
839 }
840 general_params.addSuggestionOptions(req, query, params);
841 query.addOrderBy('suggestion.initiative_id, suggestion.id');
842 general_params.addLimitAndOffset(query, params);
843 db.query(conn, req, res, query, function (result, conn) {
844 var result = { result: result.rows }
845 includes = [];
846 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
847 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
848 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
849 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
850 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
851 addRelatedData(conn, req, res, result, includes);
852 });
853 });
854 },
856 '/opinion': function (conn, req, res, params) {
857 requireAccessLevel(conn, req, res, 'pseudonym', function() {
858 var fields = ['opinion.initiative_id', 'opinion.suggestion_id', 'opinion.member_id', 'opinion.degree', 'opinion.fulfilled']
859 var query = new selector.Selector();
860 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');
861 fields.forEach( function(field) {
862 query.addField(field, null, ['grouped']);
863 });
864 general_params.addMemberOptions(req, query, params);
865 general_params.addSuggestionOptions(req, query, params);
866 query.addOrderBy = ['opinion.initiative_id, opinion.suggestion_id, opinion.member_id'];
867 general_params.addLimitAndOffset(query, params);
868 db.query(conn, req, res, query, function (result, conn) {
869 var result = { result: result.rows }
870 includes = [];
871 if (params.include_suggestions) includes.push({ class: 'suggestion', objects: 'result'});
872 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'suggestions'});
873 if (params.include_issues) includes.push({ class: 'issue', objects: 'initiatives'});
874 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
875 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
876 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
877 addRelatedData(conn, req, res, result, includes);
878 });
879 });
880 },
882 '/delegation': function (conn, req, res, params) {
883 requireAccessLevel(conn, req, res, 'pseudonym', function() {
884 var fields = ['delegation.id', 'delegation.truster_id', 'delegation.trustee_id', 'delegation.scope', 'delegation.area_id', 'delegation.issue_id', 'delegation.unit_id'];
885 var query = new selector.Selector();
886 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');
887 fields.forEach( function(field) {
888 query.addField(field, null, ['grouped']);
889 });
890 if (params.direction) {
891 switch (params.direction) {
892 case 'in':
893 query.join('member', null, 'member.id = delegation.trustee_id');
894 break;
895 case 'out':
896 query.join('member', null, 'member.id = delegation.truster_id');
897 break;
898 default:
899 respond('json', conn, req, res, 'unprocessable', 'Direction must be "in" or "out" if set.');
900 }
901 } else {
902 query.join('member', null, 'member.id = delegation.truster_id OR member.id = delegation.trustee_id');
903 }
904 general_params.addMemberOptions(req, query, params);
905 general_params.addIssueOptions(req, query, params);
906 if (params.scope) {
907 query.addWhere(['delegation.scope IN (??)', params.scope.split(',')]);
908 };
909 query.addOrderBy = ['delegation.id'];
910 general_params.addLimitAndOffset(query, params);
911 db.query(conn, req, res, query, function (result, conn) {
912 respond('json', conn, req, res, 'ok', { result: result.rows });
913 });
914 });
915 },
917 '/vote': function (conn, req, res, params) {
918 requireAccessLevel(conn, req, res, 'pseudonym', function() {
919 var query = new selector.Selector();
920 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');
921 query.addField('vote.*');
922 query.addWhere('issue.closed_at NOTNULL');
923 general_params.addMemberOptions(req, query, params);
924 general_params.addInitiativeOptions(req, query, params);
925 general_params.addLimitAndOffset(query, params);
926 db.query(conn, req, res, query, function (result, conn) {
927 respond('json', conn, req, res, 'ok', { result: result.rows });
928 });
929 });
930 },
932 '/event': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
933 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'];
934 var query = new selector.Selector();
935 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');
936 fields.forEach( function(field) {
937 query.addField(field, null, ['grouped']);
938 });
939 general_params.addMemberOptions(req, query, params);
940 general_params.addInitiativeOptions(req, query, params);
941 query.addOrderBy('event.id');
942 general_params.addLimitAndOffset(query, params);
943 db.query(conn, req, res, query, function (events, conn) {
944 var result = { result: events.rows }
945 includes = [];
946 if (params.include_initiatives) includes.push({ class: 'initiative', objects: 'result'});
947 if (params.include_issues) includes.push({ class: 'issue', objects: 'result'});
948 if (params.include_areas) includes.push({ class: 'area', objects: 'issues'});
949 if (params.include_units) includes.push({ class: 'unit', objects: 'areas'});
950 if (params.include_policies) includes.push({ class: 'policy', objects: 'issues' });
951 addRelatedData(conn, req, res, result, includes);
952 });
953 }); },
955 // TODO add interfaces for data structure:
956 // event requireAccessLevel(conn, req, res, 'member');
957 // ignored_member requireAccessLevel(conn, req, res, 'member');
958 // ignored_initiative requireAccessLevel(conn, req, res, 'member');
959 // setting requireAccessLevel(conn, req, res, 'member');
961 };
963 // ==========================================================================
964 // POST methods
965 // ==========================================================================
969 exports.post = {
971 '/echo_test': function (conn, req, res, params) { requireAccessLevel(conn, req, res, 'anonymous', function() {
972 respond('json', conn, req, res, 'ok', { result: params });
973 }); },
975 '/register_test': function (conn, req, res, params) {
976 var understood = params.understood;
977 var member_login = randomString(16);
978 var member_name = params.name;
979 var member_password = randomString(16);
980 var member_notify_email = params.email;
981 var member_notify_email_secret = randomString(24);
982 var api_key_member = randomString(24);
983 var api_key_full = randomString(24);
984 var api_key_pseudonym = randomString(24);
985 var api_key_anonymous = randomString(24);
987 if (understood != 'understood') {
988 respond('html', conn, req, res, 'unprocessable', null, 'You didn\'t checked the checkbox! Please hit back in your browser and try again.');
989 return;
990 }
992 // add member
993 var query = new selector.SQLInsert('member');
994 query.addValues({
995 login: member_login,
996 password: member_password, // TODO hashing of password
997 notify_email_unconfirmed: member_notify_email,
998 notify_email_secret: member_notify_email_secret,
999 name: member_name
1000 });
1001 query.addReturning('id');
1002 db.query(conn, req, res, query, function (result, conn) {
1003 var member_id = result.rows[0].id;
1005 // add privilege for root unit
1006 var query = new selector.SQLInsert('privilege');
1007 query.addValues({ unit_id: 1, member_id: member_id, voting_right: true });
1008 db.query(conn, req, res, query, function (result, conn) {
1010 var location = params.location;
1011 var unit_id;
1012 switch(location) {
1013 case 'earth':
1014 unit_id = 3;
1015 break;
1016 case 'moon':
1017 unit_id = 4;
1018 break;
1019 case 'mars':
1020 unit_id = 5;
1021 break;
1024 // add privilege for selected planet
1025 var query = new selector.SQLInsert('privilege');
1026 query.addValues({ unit_id: unit_id, member_id: member_id, voting_right: true });
1027 db.query(conn, req, res, query, function (result, conn) {
1029 // add application key
1030 var query = new selector.SQLInsert('member_application');
1031 query.addValues({
1032 member_id: member_id,
1033 name: 'member',
1034 comment: 'access_level member',
1035 access_level: 'member',
1036 key: api_key_member
1037 });
1038 query.addReturning('id');
1040 db.query(conn, req, res, query, function (result, conn) {
1042 nodemail.sendmail = '/usr/bin/sendmail';
1044 // send email to user
1045 nodemailer.send_mail({
1046 from: config.mail.from,
1047 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1048 to: member_notify_email,
1049 body: "\
1050 Hello " + member_name + ",\n\
1051 \n\
1052 thank you for registering at the public alpha test of the LiquidFeedback\n\
1053 application programming interface. To complete the registration process,\n\
1054 you need to confirm your email address by opening the following URL:\n\
1055 \n\
1056 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1057 \n\
1058 \n\
1059 After you've confirmed your email address, your account will be automatically\n\
1060 activated.\n\
1061 \n\
1062 Your account name is: " + member_name + "\n\
1063 \n\
1064 \n\
1065 You will need the following login and password to register and unregister\n\
1066 applications for your account later. This function is currently not\n\
1067 implemented, but please keep the credentials for future use.\n\
1068 \n\
1069 Account ID: " + member_id + "\n\
1070 Login: " + member_login + "\n\
1071 Password: " + member_password + "\n\
1072 \n\
1073 \n\
1074 To make you able to actually access the API interface, we added the following\n\
1075 application key with full member access privileges to your account:\n\
1076 \n\
1077 API Key: " + api_key_member + "\n\
1078 \n\
1079 \n\
1080 The base address of the public test is: " + config.public_url_path + "\n\
1081 \n\
1082 The programming interface is described in the LiquidFeedback API\n\
1083 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1084 \n\
1085 The current implementation status of lfapi is published at the LiquidFeedback\n\
1086 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1087 \n\
1088 If you have any questions or suggestions, please use our public mailing list\n\
1089 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1090 \n\
1091 For issues regarding your test account, contact us via email at\n\
1092 lqfb-maintainers@public-software-group.org\n\
1093 \n\
1094 \n\
1095 Sincerely,\n\
1096 \n\
1097 Your LiquidFeedback maintainers",
1098 },
1099 function(err, result){
1100 if(err){ console.log(err); }
1101 });
1103 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1104 });
1105 });
1106 });
1107 });
1108 },
1110 /*
1111 '/register': function (conn, req, res, params) {
1112 var invite_key = params.invite_key;
1113 var login = params.login;
1114 var password = params.password;
1115 var name = params.name;
1116 var notify_email = params.notify_email;
1117 if (!invite_key) {
1118 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1119 return;
1120 };
1121 if (!login) {
1122 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1123 return;
1124 };
1125 if (!password) {
1126 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1127 return;
1128 };
1129 if (!name) {
1130 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1131 return;
1132 };
1133 if (!notify_email) {
1134 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1135 return;
1136 };
1137 // check if akey is valid and get member_id for akey
1138 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) {
1139 if (result.rows.length != 1) {
1140 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1141 return;
1142 };
1143 var member_id = result.rows[0].id;
1144 // check if name is available
1145 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1146 if (result.rows.length > 0) {
1147 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1148 return;
1149 };
1150 // check if login is available
1151 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1152 if (result.rows.length > 0) {
1153 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1154 return;
1155 };
1156 var query = { update: 'member', set: { activation: 'now', active: true, } };
1158 });
1159 });
1160 });
1161 },
1162 */
1164 '/session': function (conn, req, res, params) {
1165 var key = params.key;
1166 if (!key) {
1167 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1168 return;
1169 };
1170 var query = new selector.Selector();
1171 query.from('member');
1172 query.join('member_application', null, 'member_application.member_id = member.id');
1173 query.addField('member.id');
1174 query.addWhere(['member.active AND member_application.key = ?', key]);
1175 if (params.interactive) {
1176 query.forUpdateOf('member');
1178 db.query(conn, req, res, query, function (result, conn) {
1179 if (result.rows.length != 1) {
1180 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1181 return;
1182 };
1183 var member_id = result.rows[0].id;
1184 var session_key = randomString(16);
1185 req.sessions[session_key] = member_id;
1186 var query;
1187 if (params.interactive) {
1188 query = new selector.SQLUpdate('member');
1189 query.addWhere(['member.id = ?', member_id]);
1190 query.addValues({ last_activity: 'now' });
1192 db.query(conn, req, res, query, function (result, conn) {
1193 respond('json', conn, req, res, 'ok', { session_key: session_key });
1194 });
1195 });
1196 },
1198 '/member': function (conn, req, res, params) {
1199 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1200 requireAccessLevel(conn, req, res, 'member', function() {
1201 var query = new selector.SQLUpdate('member');
1202 query.addWhere(['member.id = ?', req.current_member_id]);
1203 fields.forEach( function(field) {
1204 if (typeof(params[field]) != 'undefined') {
1205 query.addValues({ field: params[field] });
1206 } else {
1207 query.addValues({ field: null });
1209 });
1210 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1211 });
1212 },
1214 '/membership': function (conn, req, res, params) {
1215 requireAccessLevel(conn, req, res, 'member', function() {
1217 // check if area_id is set
1218 var area_id = parseInt(params.area_id);
1219 if (!area_id) {
1220 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1221 return;
1224 // delete membership
1225 if (params.delete) {
1226 var query;
1227 query = new selector.SQLDelete('membership');
1228 query.addWhere(['area_id = ?', area_id]);
1229 query.addWhere(['member_id = ?', req.current_member_id]);
1230 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1232 // add membership
1233 } else {
1235 // lock member for upsert
1236 lockMemberById(conn, req, res, req.current_member_id, function() {
1238 // check and lock privilege
1239 requireAreaPrivilege(conn, req, res, area_id, function() {
1241 // upsert membership
1242 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1243 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1244 db.query(conn, req, res, query, function(result) {
1245 respond('json', conn, req, res, 'ok');
1246 });
1247 });
1248 });
1250 });
1251 },
1253 '/interest': function (conn, req, res, params) {
1254 requireAccessLevel(conn, req, res, 'member', function() {
1255 var query;
1257 // check if issue_id is set
1258 var issue_id = parseInt(params.issue_id);
1259 if (!issue_id) {
1260 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1261 return;
1264 // lock member for upsert
1265 lockMemberById(conn, req, res, req.current_member_id, function() {
1267 // delete interest
1268 if (params.delete) {
1270 // check issue state
1271 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1273 // delete interest
1274 query = new selector.SQLDelete('interest');
1275 query.addWhere(['issue_id = ?', issue_id]);
1276 query.addWhere(['member_id = ?', req.current_member_id]);
1277 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1278 });
1280 // add interest
1281 } else {
1283 // check and lock privilege
1284 requireIssuePrivilege(conn, req, res, issue_id, function() {
1286 // check issue state
1287 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1289 // upsert interest
1290 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1291 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1292 db.query(conn, req, res, query, function(result) {
1293 respond('json', conn, req, res, 'ok');
1294 });
1295 });
1296 });
1297 };
1298 });
1299 });
1300 },
1302 '/issue_comment': function (conn, req, res, params) {
1303 requireAccessLevel(conn, req, res, 'member', function() {
1305 var issue_id = parseInt(params.issue_id);
1306 var formatting_engine = params.formatting_engine
1307 var content = params.content;
1309 if (!issue_id) {
1310 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1311 return;
1314 // delete issue comment
1315 if (params.delete) {
1316 var query;
1317 query = new selector.SQLDelete('issue_comment');
1318 query.addWhere(['issue_id = ?', params.issue_id]);
1319 query.addWhere(['member_id = ?', req.current_member_id]);
1320 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1322 // upsert issue comment
1323 } else {
1325 // check if formatting engine is supplied and valid
1326 if (!formatting_engine) {
1327 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1328 return;
1329 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1330 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1331 return;
1332 };
1334 // check if content is supplied
1335 if (!content) {
1336 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1337 return;
1340 // lock member for upsert
1341 lockMemberById(conn, req, res, req.current_member_id, function() {
1343 // check and lock privilege
1344 requireIssuePrivilege(conn, req, res, issue_id, function() {
1346 // upsert issue comment
1347 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1348 query.addValues({
1349 issue_id: issue_id,
1350 member_id: req.current_member_id,
1351 changed: 'now',
1352 formatting_engine: formatting_engine,
1353 content: content
1354 });
1356 db.query(conn, req, res, query, function(result) {
1357 respond('json', conn, req, res, 'ok');
1358 });
1360 });
1361 });
1365 });
1366 },
1368 '/voting_comment': function (conn, req, res, params) {
1369 requireAccessLevel(conn, req, res, 'member', function() {
1371 var issue_id = parseInt(params.issue_id);
1372 var formatting_engine = params.formatting_engine
1373 var content = params.content;
1375 if (!issue_id) {
1376 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1377 return;
1381 // delete voting comment
1382 if (params.delete) {
1383 var query;
1384 query = new selector.SQLDelete('voting_comment');
1385 query.addWhere(['issue_id = ?', params.issue_id]);
1386 query.addWhere(['member_id = ?', req.current_member_id]);
1387 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1389 // upsert voting comment
1390 } else {
1392 // check if formatting engine is supplied and valid
1393 if (!formatting_engine) {
1394 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1395 return;
1396 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1397 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1398 return;
1399 };
1401 // check if content is supplied
1402 if (!content) {
1403 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1404 return;
1407 // lock member for upsert
1408 lockMemberById(conn, req, res, req.current_member_id, function() {
1410 // check and lock privilege
1411 requireIssuePrivilege(conn, req, res, issue_id, function() {
1413 // check issue state
1414 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1416 // upsert voting comment
1417 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1418 query.addValues({
1419 issue_id: issue_id,
1420 member_id: req.current_member_id,
1421 changed: 'now',
1422 formatting_engine: formatting_engine,
1423 content: content
1424 });
1426 db.query(conn, req, res, query, function(result) {
1427 respond('json', conn, req, res, 'ok');
1428 });
1430 });
1431 });
1432 })
1433 };
1434 });
1435 },
1437 '/supporter': function (conn, req, res, params) {
1438 requireAccessLevel(conn, req, res, 'member', function() {
1439 var initiative_id = parseInt(params.initiative_id);
1440 var draft_id = parseInt(params.draft_id);
1442 // check if needed arguments are supplied
1443 if (!initiative_id) {
1444 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1445 return;
1448 if (!draft_id) {
1449 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1450 return;
1453 // lock member for upsert
1454 lockMemberById(conn, req, res, req.current_member_id, function() {
1456 // delete supporter
1457 if (params.delete) {
1459 // check issue state
1460 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1462 // delete supporter
1463 var query = new selector.SQLDelete('supporter');
1464 query.addWhere(['initiative_id = ?', initiative_id]);
1465 query.addWhere(['member_id = ?', req.current_member_id]);
1466 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1468 });
1470 // upsert supporter
1471 } else {
1473 // check and lock privilege
1474 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1476 // check issue state
1477 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1479 // check if given draft is the current one
1480 var query = new selector.Selector('current_draft');
1481 query.addField('NULL');
1482 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1483 query.addWhere(['current_draft.id = ?', draft_id]);
1485 db.query(conn, req, res, query, function(result) {
1486 if (result.rows.length != 1) {
1487 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1488 return;
1491 // upsert supporter
1492 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1493 query.addValues({
1494 initiative_id: initiative_id,
1495 member_id: req.current_member_id,
1496 draft_id: draft_id
1497 });
1499 db.query(conn, req, res, query, function(result) {
1500 respond('json', conn, req, res, 'ok');
1501 });
1503 });
1504 });
1505 });
1506 };
1507 });
1508 });
1509 },
1511 '/draft': function (conn, req, res, params) {
1512 requireAccessLevel(conn, req, res, 'member', function() {
1513 var area_id = parseInt(params.area_id);
1514 var policy_id = parseInt(params.policy_id);
1515 var issue_id = parseInt(params.issue_id);
1516 var initiative_id = parseInt(params.initiative_id);
1517 var initiative_name = params.initiative_name;
1518 var initiative_discussion_url = params.initiative_discussion_url;
1519 var formatting_engine = params.formatting_engine;
1520 var content = params.content;
1522 if (!initiative_discussion_url) initiative_discussion_url = null;
1524 // check parameters
1525 if (!formatting_engine) {
1526 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1527 return;
1528 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1529 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1530 return;
1531 };
1533 if (!content) {
1534 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1535 return;
1536 };
1538 lockMemberById(conn, req, res, req.current_member_id, function() {
1540 // new draft in new initiative in new issue
1541 if (area_id && !issue_id && !initiative_id) {
1543 // check parameters for new issue
1544 if (!policy_id) {
1545 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1546 return;
1549 if (!initiative_name) {
1550 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1551 return;
1554 requireAreaPrivilege(conn, req, res, area_id, function() {
1556 // check if policy is allowed in this area and if area and policy are active
1557 var query = new selector.Selector();
1558 query.from('allowed_policy');
1559 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1560 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1561 query.addField('NULL');
1562 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1563 db.query(conn, req, res, query, function (result, conn) {
1564 if (result.rows.length != 1) {
1565 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.');
1566 return;
1567 };
1569 // check contingent
1570 requireContingentLeft(conn, req, res, true, function() {
1572 // insert new issue
1573 var query = new selector.SQLInsert('issue');
1574 query.addValues({
1575 area_id: area_id,
1576 policy_id: policy_id
1577 });
1578 query.addReturning('id');
1579 db.query(conn, req, res, query, function(result) {
1580 var issue_id = result.rows[0].id;
1582 // insert new initiative
1583 var query = new selector.SQLInsert('initiative');
1584 query.addValues({
1585 issue_id: issue_id,
1586 name: initiative_name,
1587 discussion_url: initiative_discussion_url
1588 });
1589 query.addReturning('id');
1590 db.query(conn, req, res, query, function(result) {
1591 var initiative_id = result.rows[0].id;
1593 // insert initiator
1594 var query = new selector.SQLInsert('initiator');
1595 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1596 db.query(conn, req, res, query, function(result) {
1598 // insert new draft
1599 var query = new selector.SQLInsert('draft');
1600 query.addValues({
1601 initiative_id: initiative_id,
1602 author_id: req.current_member_id,
1603 formatting_engine: formatting_engine,
1604 content: content
1605 });
1606 query.addReturning('id');
1607 db.query(conn, req, res, query, function (result, conn) {
1608 var draft_id = result.rows[0].id;
1610 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1611 });
1612 });
1613 });
1614 });
1615 });
1616 });
1617 });
1619 // new draft in new initiative in existant issue
1620 } else if (issue_id && !area_id && !initiative_id) {
1622 if (!initiative_name) {
1623 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1624 return;
1627 // check privilege
1628 requireIssuePrivilege(conn, req, res, issue_id, function() {
1630 // check issue state
1631 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1633 // check contingent
1634 requireContingentLeft(conn, req, res, true, function() {
1636 // insert initiative
1637 var query = new selector.SQLInsert('initiative');
1638 query.addValues({
1639 issue_id: issue_id,
1640 name: initiative_name,
1641 discussion_url: initiative_discussion_url
1642 });
1643 query.addReturning('id');
1644 db.query(conn, req, res, query, function(result) {
1645 var initiative_id = result.rows[0].id;
1647 // insert initiator
1648 var query = new selector.SQLInsert('initiator');
1649 query.addValues({
1650 initiative_id: initiative_id,
1651 member_id: req.current_member_id,
1652 accepted: true
1653 });
1654 db.query(conn, req, res, query, function(result) {
1656 // insert draft
1657 var query = new selector.SQLInsert('draft');
1658 query.addValues({
1659 initiative_id: initiative_id,
1660 author_id: req.current_member_id,
1661 formatting_engine: formatting_engine,
1662 content: content
1663 });
1664 query.addReturning('id');
1665 db.query(conn, req, res, query, function (result, conn) {
1667 var draft_id = result.rows[0].id;
1668 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1670 });
1671 });
1672 });
1673 });
1674 });
1675 });
1677 // new draft in existant initiative
1678 } else if (initiative_id && !area_id && !issue_id ) {
1680 // check privilege
1681 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1683 // check issue state
1684 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1687 // get initiator
1688 var query = new selector.Selector();
1689 query.from('initiator');
1690 query.addField('accepted');
1691 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1692 db.query(conn, req, res, query, function (result, conn) {
1694 // if member is not initiator, deny creating new draft
1695 if (result.rows.length != 1) {
1696 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1697 return;
1699 var initiator = result.rows[0];
1700 if (!initiator.accepted) {
1701 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.');
1702 return;
1703 };
1705 // check contingent
1706 requireContingentLeft(conn, req, res, false, function() {
1708 // insert new draft
1709 var query = new selector.SQLInsert('draft');
1710 query.addValues({
1711 initiative_id: initiative_id,
1712 author_id: req.current_member_id,
1713 formatting_engine: formatting_engine,
1714 content: content
1715 });
1716 query.addReturning('id');
1717 db.query(conn, req, res, query, function (result, conn) {
1719 var draft_id = result.rows[0].id;
1720 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1721 });
1722 });
1723 });
1724 });
1725 });
1727 // none of them (invalid request)
1728 } else {
1729 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1730 };
1732 });
1733 });
1734 },
1736 '/suggestion': function (conn, req, res, params) {
1737 requireAccessLevel(conn, req, res, 'member', function() {
1738 // TODO
1739 });
1740 },
1742 '/opinion': function (conn, req, res, params) {
1743 requireAccessLevel(conn, req, res, 'member', function() {
1744 // TODO
1745 });
1746 },
1748 '/delegation': function (conn, req, res, params) {
1749 requireAccessLevel(conn, req, res, 'member', function() {
1750 var unit_id = parseInt(params.unit_id);
1751 var area_id = parseInt(params.area_id);
1752 var issue_id = parseInt(params.issue_id);
1753 var trustee_id;
1755 if (params.trustee_id == '') {
1756 trustee_id = null;
1757 } else {
1758 trustee_id = parseInt(params.trustee_id);
1761 lockMemberById(conn, req, res, req.current_member_id, function() {
1763 if (params.delete) {
1764 var query = new selector.SQLDelete('delegation')
1765 if (unit_id && !area_id && !issue_id) {
1766 query.addWhere(['unit_id = ?', unit_id]);
1767 } else if (!unit_id && area_id && !issue_id) {
1768 query.addWhere(['area_id = ?', area_id]);
1769 } else if (!unit_id && !area_id && issue_id) {
1770 query.addWhere(['issue_id = ?', issue_id]);
1771 } else {
1772 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1773 return;
1775 query.addWhere(['truster_id = ?', req.current_member_id]);
1776 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1777 } else {
1778 var query = new selector.Upserter('delegation', ['truster_id']);
1779 query.addValues({
1780 truster_id: req.current_member_id,
1781 trustee_id: trustee_id
1782 });
1783 if (unit_id && !area_id && !issue_id) {
1785 // check privilege
1786 requireUnitPrivilege(conn, req, res, unit_id, function() {
1788 query.addKeys(['unit_id'])
1789 query.addValues({ unit_id: unit_id, scope: 'unit' });
1790 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1791 });
1793 } else if (!unit_id && area_id && !issue_id) {
1795 // check privilege
1796 requireAreaPrivilege(conn, req, res, area_id, function() {
1798 query.addKeys(['area_id'])
1799 query.addValues({ area_id: area_id, scope: 'area' });
1800 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1801 });
1803 } else if (!unit_id && !area_id && issue_id) {
1805 // check privilege
1806 requireIssuePrivilege(conn, req, res, issue_id, function() {
1808 // check issue state
1809 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1811 query.addKeys(['issue_id'])
1812 query.addValues({ issue_id: issue_id, scope: 'issue' });
1813 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1814 });
1815 });
1816 } else {
1817 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1818 return;
1822 });
1824 });
1825 },
1827 };

Impressum / About Us