lfapi

view lfapi/main.js @ 4:77b761569ae2

Removed workaround not needed anymore with current node-postgres.
author bsw
date Mon Oct 24 20:32:49 2011 +0200 (2011-10-24)
parents e69609a3c98a
children ef8aff2e0f67
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 = 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
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 // send email to user
1043 email.send({
1044 host : config.mail.smtp_host,
1045 port: config.mail.smtp_port,
1046 ssl: config.mail.smtp_ssl,
1047 domain: config.mail.smtp_domain,
1048 authentication: config.mail.smtp_authentication,
1049 username: config.mail.smtp_username,
1050 password: config.mail.smtp_password,
1051 from: config.mail.from,
1052 subject: config.mail.subject_prefix + "Your LiquidFeedback API alpha test account needs confirmation",
1053 to: member_notify_email,
1054 body: "\
1055 Hello " + member_name + ",\n\
1056 \n\
1057 thank you for registering at the public alpha test of the LiquidFeedback\n\
1058 application programming interface. To complete the registration process,\n\
1059 you need to confirm your email address by opening the following URL:\n\
1060 \n\
1061 " + config.public_url_path + "register_test_confirm?secret=" + member_notify_email_secret + "\n\
1062 \n\
1063 \n\
1064 After you've confirmed your email address, your account will be automatically\n\
1065 activated.\n\
1066 \n\
1067 Your account name is: " + member_name + "\n\
1068 \n\
1069 \n\
1070 You will need the following login and password to register and unregister\n\
1071 applications for your account later. This function is currently not\n\
1072 implemented, but please keep the credentials for future use.\n\
1073 \n\
1074 Account ID: " + member_id + "\n\
1075 Login: " + member_login + "\n\
1076 Password: " + member_password + "\n\
1077 \n\
1078 \n\
1079 To make you able to actually access the API interface, we added the following\n\
1080 application key with full member access privileges to your account:\n\
1081 \n\
1082 API Key: " + api_key_member + "\n\
1083 \n\
1084 \n\
1085 The base address of the public test is: " + config.public_url_path + "\n\
1086 \n\
1087 The programming interface is described in the LiquidFeedback API\n\
1088 specification: http://dev.liquidfeedback.org/trac/lf/wiki/API\n\
1089 \n\
1090 The current implementation status of lfapi is published at the LiquidFeedback\n\
1091 API server page: http://dev.liquidfeedback.org/trac/lf/wiki/lfapi\n\
1092 \n\
1093 If you have any questions or suggestions, please use our public mailing list\n\
1094 at http://dev.liquidfeedback.org/cgi-bin/mailman/listinfo/main\n\
1095 \n\
1096 For issues regarding your test account, contact us via email at\n\
1097 lqfb-maintainers@public-software-group.org\n\
1098 \n\
1099 \n\
1100 Sincerely,\n\
1101 \n\
1102 Your LiquidFeedback maintainers",
1103 },
1104 function(err, result){
1105 if(err){ console.log(err); }
1106 });
1108 respond('html', conn, req, res, 'ok', 'Account created. Please check your mailbox!<br /><br /><br /><a href="/">Back to start page</a>');
1109 });
1110 });
1111 });
1112 });
1113 },
1115 /*
1116 '/register': function (conn, req, res, params) {
1117 var invite_key = params.invite_key;
1118 var login = params.login;
1119 var password = params.password;
1120 var name = params.name;
1121 var notify_email = params.notify_email;
1122 if (!invite_key) {
1123 respond('json', conn, req, res, 'unprocessable', null, 'No invite_key supplied.');
1124 return;
1125 };
1126 if (!login) {
1127 respond('json', conn, req, res, 'unprocessable', null, 'No login supplied.');
1128 return;
1129 };
1130 if (!password) {
1131 respond('json', conn, req, res, 'unprocessable', null, 'No password supplied.');
1132 return;
1133 };
1134 if (!name) {
1135 respond('json', conn, req, res, 'unprocessable', null, 'No name supplied.');
1136 return;
1137 };
1138 if (!notify_email) {
1139 respond('json', conn, req, res, 'unprocessable', null, 'No notify_email supplied.');
1140 return;
1141 };
1142 // check if akey is valid and get member_id for akey
1143 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) {
1144 if (result.rows.length != 1) {
1145 respond('json', conn, req, res, 'forbidden', null, 'Supplied invite_key is not valid.');
1146 return;
1147 };
1148 var member_id = result.rows[0].id;
1149 // check if name is available
1150 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.name = ' + db.pgEncode(name)] }, function (result, conn) {
1151 if (result.rows.length > 0) {
1152 respond('json', conn, req, res, 'forbidden', null, 'Login name is not available, choose another one.');
1153 return;
1154 };
1155 // check if login is available
1156 db.query(conn, req, res, { select: ['NULL'], from: ['member'], where: ['member.login = ' + db.pgEncode(login)] }, function (result, conn) {
1157 if (result.rows.length > 0) {
1158 respond('json', conn, req, res, 'forbidden', null, 'Name is not available, choose another one.');
1159 return;
1160 };
1161 var query = { update: 'member', set: { activation: 'now', active: true, } };
1163 });
1164 });
1165 });
1166 },
1167 */
1169 '/session': function (conn, req, res, params) {
1170 var key = params.key;
1171 if (!key) {
1172 respond('json', conn, req, res, 'unprocessable', null, 'No application key supplied.');
1173 return;
1174 };
1175 var query = new selector.Selector();
1176 query.from('member');
1177 query.join('member_application', null, 'member_application.member_id = member.id');
1178 query.addField('member.id');
1179 query.addWhere(['member.active AND member_application.key = ?', key]);
1180 if (params.interactive) {
1181 query.forUpdateOf('member');
1183 db.query(conn, req, res, query, function (result, conn) {
1184 if (result.rows.length != 1) {
1185 respond('json', conn, req, res, 'forbidden', null, 'Supplied application key is not valid.');
1186 return;
1187 };
1188 var member_id = result.rows[0].id;
1189 var session_key = randomString(16);
1190 req.sessions[session_key] = member_id;
1191 var query;
1192 if (params.interactive) {
1193 query = new selector.SQLUpdate('member');
1194 query.addWhere(['member.id = ?', member_id]);
1195 query.addValues({ last_activity: 'now' });
1197 db.query(conn, req, res, query, function (result, conn) {
1198 respond('json', conn, req, res, 'ok', { session_key: session_key });
1199 });
1200 });
1201 },
1203 '/member': function (conn, req, res, params) {
1204 var fields = ['organizational_unit', 'internal_posts', 'realname', 'birthday', 'address', 'email', 'xmpp_address', 'website', 'phone', 'mobile_phone', 'profession', 'external_memberships', 'external_posts', 'statement']
1205 requireAccessLevel(conn, req, res, 'member', function() {
1206 var query = new selector.SQLUpdate('member');
1207 query.addWhere(['member.id = ?', req.current_member_id]);
1208 fields.forEach( function(field) {
1209 if (typeof(params[field]) != 'undefined') {
1210 query.addValues({ field: params[field] });
1211 } else {
1212 query.addValues({ field: null });
1214 });
1215 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1216 });
1217 },
1219 '/membership': function (conn, req, res, params) {
1220 requireAccessLevel(conn, req, res, 'member', function() {
1222 // check if area_id is set
1223 var area_id = parseInt(params.area_id);
1224 if (!area_id) {
1225 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an area_id.');
1226 return;
1229 // delete membership
1230 if (params.delete) {
1231 var query;
1232 query = new selector.SQLDelete('membership');
1233 query.addWhere(['area_id = ?', area_id]);
1234 query.addWhere(['member_id = ?', req.current_member_id]);
1235 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1237 // add membership
1238 } else {
1240 // lock member for upsert
1241 lockMemberById(conn, req, res, req.current_member_id, function() {
1243 // check and lock privilege
1244 requireAreaPrivilege(conn, req, res, area_id, function() {
1246 // upsert membership
1247 var query = new selector.Upserter('membership', ['area_id', 'member_id']);
1248 query.addValues({ area_id: area_id, member_id: req.current_member_id });
1249 db.query(conn, req, res, query, function(result) {
1250 respond('json', conn, req, res, 'ok');
1251 });
1252 });
1253 });
1255 });
1256 },
1258 '/interest': function (conn, req, res, params) {
1259 requireAccessLevel(conn, req, res, 'member', function() {
1260 var query;
1262 // check if issue_id is set
1263 var issue_id = parseInt(params.issue_id);
1264 if (!issue_id) {
1265 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1266 return;
1269 // lock member for upsert
1270 lockMemberById(conn, req, res, req.current_member_id, function() {
1272 // delete interest
1273 if (params.delete) {
1275 // check issue state
1276 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1278 // delete interest
1279 query = new selector.SQLDelete('interest');
1280 query.addWhere(['issue_id = ?', issue_id]);
1281 query.addWhere(['member_id = ?', req.current_member_id]);
1282 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1283 });
1285 // add interest
1286 } else {
1288 // check and lock privilege
1289 requireIssuePrivilege(conn, req, res, issue_id, function() {
1291 // check issue state
1292 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1294 // upsert interest
1295 var query = new selector.Upserter('interest', ['issue_id', 'member_id']);
1296 query.addValues({ issue_id: issue_id, member_id: req.current_member_id });
1297 db.query(conn, req, res, query, function(result) {
1298 respond('json', conn, req, res, 'ok');
1299 });
1300 });
1301 });
1302 };
1303 });
1304 });
1305 },
1307 '/issue_comment': function (conn, req, res, params) {
1308 requireAccessLevel(conn, req, res, 'member', function() {
1310 var issue_id = parseInt(params.issue_id);
1311 var formatting_engine = params.formatting_engine
1312 var content = params.content;
1314 if (!issue_id) {
1315 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1316 return;
1319 // delete issue comment
1320 if (params.delete) {
1321 var query;
1322 query = new selector.SQLDelete('issue_comment');
1323 query.addWhere(['issue_id = ?', params.issue_id]);
1324 query.addWhere(['member_id = ?', req.current_member_id]);
1325 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1327 // upsert issue comment
1328 } else {
1330 // check if formatting engine is supplied and valid
1331 if (!formatting_engine) {
1332 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1333 return;
1334 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1335 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1336 return;
1337 };
1339 // check if content is supplied
1340 if (!content) {
1341 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1342 return;
1345 // lock member for upsert
1346 lockMemberById(conn, req, res, req.current_member_id, function() {
1348 // check and lock privilege
1349 requireIssuePrivilege(conn, req, res, issue_id, function() {
1351 // upsert issue comment
1352 var query = new selector.Upserter('issue_comment', ['issue_id', 'member_id']);
1353 query.addValues({
1354 issue_id: issue_id,
1355 member_id: req.current_member_id,
1356 changed: 'now',
1357 formatting_engine: formatting_engine,
1358 content: content
1359 });
1361 db.query(conn, req, res, query, function(result) {
1362 respond('json', conn, req, res, 'ok');
1363 });
1365 });
1366 });
1370 });
1371 },
1373 '/voting_comment': function (conn, req, res, params) {
1374 requireAccessLevel(conn, req, res, 'member', function() {
1376 var issue_id = parseInt(params.issue_id);
1377 var formatting_engine = params.formatting_engine
1378 var content = params.content;
1380 if (!issue_id) {
1381 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an issue_id.');
1382 return;
1386 // delete voting comment
1387 if (params.delete) {
1388 var query;
1389 query = new selector.SQLDelete('voting_comment');
1390 query.addWhere(['issue_id = ?', params.issue_id]);
1391 query.addWhere(['member_id = ?', req.current_member_id]);
1392 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1394 // upsert voting comment
1395 } else {
1397 // check if formatting engine is supplied and valid
1398 if (!formatting_engine) {
1399 respond('json', conn, req, res, 'unprocessable', null, 'No formatting engine supplied.');
1400 return;
1401 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1402 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1403 return;
1404 };
1406 // check if content is supplied
1407 if (!content) {
1408 respond('json', conn, req, res, 'unprocessable', null, 'No content supplied.');
1409 return;
1412 // lock member for upsert
1413 lockMemberById(conn, req, res, req.current_member_id, function() {
1415 // check and lock privilege
1416 requireIssuePrivilege(conn, req, res, issue_id, function() {
1418 // check issue state
1419 requireIssueState(conn, req, res, issue_id, ['voting', 'finished_with_winner', 'finished_without_winner'], function() {
1421 // upsert voting comment
1422 var query = new selector.Upserter('voting_comment', ['issue_id', 'member_id']);
1423 query.addValues({
1424 issue_id: issue_id,
1425 member_id: req.current_member_id,
1426 changed: 'now',
1427 formatting_engine: formatting_engine,
1428 content: content
1429 });
1431 db.query(conn, req, res, query, function(result) {
1432 respond('json', conn, req, res, 'ok');
1433 });
1435 });
1436 });
1437 })
1438 };
1439 });
1440 },
1442 '/supporter': function (conn, req, res, params) {
1443 requireAccessLevel(conn, req, res, 'member', function() {
1444 var initiative_id = parseInt(params.initiative_id);
1445 var draft_id = parseInt(params.draft_id);
1447 // check if needed arguments are supplied
1448 if (!initiative_id) {
1449 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an initiative_id.');
1450 return;
1453 if (!draft_id) {
1454 respond('json', conn, req, res, 'unprocessable', null, 'You need to supply an draft_id.');
1455 return;
1458 // lock member for upsert
1459 lockMemberById(conn, req, res, req.current_member_id, function() {
1461 // delete supporter
1462 if (params.delete) {
1464 // check issue state
1465 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1467 // delete supporter
1468 var query = new selector.SQLDelete('supporter');
1469 query.addWhere(['initiative_id = ?', initiative_id]);
1470 query.addWhere(['member_id = ?', req.current_member_id]);
1471 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1473 });
1475 // upsert supporter
1476 } else {
1478 // check and lock privilege
1479 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1481 // check issue state
1482 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion', 'verification'], function() {
1484 // check if given draft is the current one
1485 var query = new selector.Selector('current_draft');
1486 query.addField('NULL');
1487 query.addWhere(['current_draft.initiative_id = ?', initiative_id]);
1488 query.addWhere(['current_draft.id = ?', draft_id]);
1490 db.query(conn, req, res, query, function(result) {
1491 if (result.rows.length != 1) {
1492 respond('json', conn, req, res, 'conflict', null, 'The draft with the supplied draft_id is not the current one anymore!');
1493 return;
1496 // upsert supporter
1497 var query = new selector.Upserter('supporter', ['initiative_id', 'member_id']);
1498 query.addValues({
1499 initiative_id: initiative_id,
1500 member_id: req.current_member_id,
1501 draft_id: draft_id
1502 });
1504 db.query(conn, req, res, query, function(result) {
1505 respond('json', conn, req, res, 'ok');
1506 });
1508 });
1509 });
1510 });
1511 };
1512 });
1513 });
1514 },
1516 '/draft': function (conn, req, res, params) {
1517 requireAccessLevel(conn, req, res, 'member', function() {
1518 var area_id = parseInt(params.area_id);
1519 var policy_id = parseInt(params.policy_id);
1520 var issue_id = parseInt(params.issue_id);
1521 var initiative_id = parseInt(params.initiative_id);
1522 var initiative_name = params.initiative_name;
1523 var initiative_discussion_url = params.initiative_discussion_url;
1524 var formatting_engine = params.formatting_engine;
1525 var content = params.content;
1527 if (!initiative_discussion_url) initiative_discussion_url = null;
1529 // check parameters
1530 if (!formatting_engine) {
1531 respond('json', conn, req, res, 'unprocessable', null, 'No formatting_engine supplied.');
1532 return;
1533 } else if (formatting_engine != 'rocketwiki' && formatting_engine != 'compat') {
1534 respond('json', conn, req, res, 'unprocessable', null, 'Invalid formatting engine supplied.');
1535 return;
1536 };
1538 if (!content) {
1539 respond('json', conn, req, res, 'unprocessable', null, 'No draft content supplied.');
1540 return;
1541 };
1543 lockMemberById(conn, req, res, req.current_member_id, function() {
1545 // new draft in new initiative in new issue
1546 if (area_id && !issue_id && !initiative_id) {
1548 // check parameters for new issue
1549 if (!policy_id) {
1550 respond('json', conn, req, res, 'unprocessable', null, 'No policy supplied.');
1551 return;
1554 if (!initiative_name) {
1555 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1556 return;
1559 requireAreaPrivilege(conn, req, res, area_id, function() {
1561 // check if policy is allowed in this area and if area and policy are active
1562 var query = new selector.Selector();
1563 query.from('allowed_policy');
1564 query.join('area', null, 'area.id = allowed_policy.area_id AND area.active');
1565 query.join('policy', null, 'policy.id = allowed_policy.policy_id AND policy.active');
1566 query.addField('NULL');
1567 query.addWhere(['area.id = ? AND policy.id = ?', area_id, policy_id]);
1568 db.query(conn, req, res, query, function (result, conn) {
1569 if (result.rows.length != 1) {
1570 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.');
1571 return;
1572 };
1574 // check contingent
1575 requireContingentLeft(conn, req, res, true, function() {
1577 // insert new issue
1578 var query = new selector.SQLInsert('issue');
1579 query.addValues({
1580 area_id: area_id,
1581 policy_id: policy_id
1582 });
1583 query.addReturning('id');
1584 db.query(conn, req, res, query, function(result) {
1585 var issue_id = result.rows[0].id;
1587 // insert new initiative
1588 var query = new selector.SQLInsert('initiative');
1589 query.addValues({
1590 issue_id: issue_id,
1591 name: initiative_name,
1592 discussion_url: initiative_discussion_url
1593 });
1594 query.addReturning('id');
1595 db.query(conn, req, res, query, function(result) {
1596 var initiative_id = result.rows[0].id;
1598 // insert initiator
1599 var query = new selector.SQLInsert('initiator');
1600 query.addValues({ initiative_id: initiative_id, member_id: req.current_member_id, accepted: true });
1601 db.query(conn, req, res, query, function(result) {
1603 // insert new draft
1604 var query = new selector.SQLInsert('draft');
1605 query.addValues({
1606 initiative_id: initiative_id,
1607 author_id: req.current_member_id,
1608 formatting_engine: formatting_engine,
1609 content: content
1610 });
1611 query.addReturning('id');
1612 db.query(conn, req, res, query, function (result, conn) {
1613 var draft_id = result.rows[0].id;
1615 respond('json', conn, req, res, 'ok', { issue_id: issue_id, initiative_id: initiative_id, draft_id: draft_id } );
1616 });
1617 });
1618 });
1619 });
1620 });
1621 });
1622 });
1624 // new draft in new initiative in existant issue
1625 } else if (issue_id && !area_id && !initiative_id) {
1627 if (!initiative_name) {
1628 respond('json', conn, req, res, 'unprocessable', null, 'No initiative name supplied.');
1629 return;
1632 // check privilege
1633 requireIssuePrivilege(conn, req, res, issue_id, function() {
1635 // check issue state
1636 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification'], function() {
1638 // check contingent
1639 requireContingentLeft(conn, req, res, true, function() {
1641 // insert initiative
1642 var query = new selector.SQLInsert('initiative');
1643 query.addValues({
1644 issue_id: issue_id,
1645 name: initiative_name,
1646 discussion_url: initiative_discussion_url
1647 });
1648 query.addReturning('id');
1649 db.query(conn, req, res, query, function(result) {
1650 var initiative_id = result.rows[0].id;
1652 // insert initiator
1653 var query = new selector.SQLInsert('initiator');
1654 query.addValues({
1655 initiative_id: initiative_id,
1656 member_id: req.current_member_id,
1657 accepted: true
1658 });
1659 db.query(conn, req, res, query, function(result) {
1661 // insert draft
1662 var query = new selector.SQLInsert('draft');
1663 query.addValues({
1664 initiative_id: initiative_id,
1665 author_id: req.current_member_id,
1666 formatting_engine: formatting_engine,
1667 content: content
1668 });
1669 query.addReturning('id');
1670 db.query(conn, req, res, query, function (result, conn) {
1672 var draft_id = result.rows[0].id;
1673 respond('json', conn, req, res, 'ok', { initiative_id: initiative_id, draft_id: draft_id } );
1675 });
1676 });
1677 });
1678 });
1679 });
1680 });
1682 // new draft in existant initiative
1683 } else if (initiative_id && !area_id && !issue_id ) {
1685 // check privilege
1686 requireInitiativePrivilege(conn, req, res, initiative_id, function() {
1688 // check issue state
1689 requireIssueStateForInitiative(conn, req, res, initiative_id, ['admission', 'discussion'], function() {
1692 // get initiator
1693 var query = new selector.Selector();
1694 query.from('initiator');
1695 query.addField('accepted');
1696 query.addWhere(['initiative_id = ? AND member_id = ?', initiative_id, req.current_member_id]);
1697 db.query(conn, req, res, query, function (result, conn) {
1699 // if member is not initiator, deny creating new draft
1700 if (result.rows.length != 1) {
1701 respond('json', conn, req, res, 'forbidden', null, 'You are not initiator of this initiative and not allowed to update its draft.');
1702 return;
1704 var initiator = result.rows[0];
1705 if (!initiator.accepted) {
1706 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.');
1707 return;
1708 };
1710 // check contingent
1711 requireContingentLeft(conn, req, res, false, function() {
1713 // insert new draft
1714 var query = new selector.SQLInsert('draft');
1715 query.addValues({
1716 initiative_id: initiative_id,
1717 author_id: req.current_member_id,
1718 formatting_engine: formatting_engine,
1719 content: content
1720 });
1721 query.addReturning('id');
1722 db.query(conn, req, res, query, function (result, conn) {
1724 var draft_id = result.rows[0].id;
1725 respond('json', conn, req, res, 'ok', { draft_id: draft_id } );
1726 });
1727 });
1728 });
1729 });
1730 });
1732 // none of them (invalid request)
1733 } else {
1734 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of area_id, issue_id or initiative_id must be supplied!');
1735 };
1737 });
1738 });
1739 },
1741 '/suggestion': function (conn, req, res, params) {
1742 requireAccessLevel(conn, req, res, 'member', function() {
1743 // TODO
1744 });
1745 },
1747 '/opinion': function (conn, req, res, params) {
1748 requireAccessLevel(conn, req, res, 'member', function() {
1749 // TODO
1750 });
1751 },
1753 '/delegation': function (conn, req, res, params) {
1754 requireAccessLevel(conn, req, res, 'member', function() {
1755 var unit_id = parseInt(params.unit_id);
1756 var area_id = parseInt(params.area_id);
1757 var issue_id = parseInt(params.issue_id);
1758 var trustee_id;
1760 if (params.trustee_id == '') {
1761 trustee_id = null;
1762 } else {
1763 trustee_id = parseInt(params.trustee_id);
1766 lockMemberById(conn, req, res, req.current_member_id, function() {
1768 if (params.delete) {
1769 var query = new selector.SQLDelete('delegation')
1770 if (unit_id && !area_id && !issue_id) {
1771 query.addWhere(['unit_id = ?', unit_id]);
1772 } else if (!unit_id && area_id && !issue_id) {
1773 query.addWhere(['area_id = ?', area_id]);
1774 } else if (!unit_id && !area_id && issue_id) {
1775 query.addWhere(['issue_id = ?', issue_id]);
1776 } else {
1777 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit, area_id, issue_id must be supplied!');
1778 return;
1780 query.addWhere(['truster_id = ?', req.current_member_id]);
1781 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1782 } else {
1783 var query = new selector.Upserter('delegation', ['truster_id']);
1784 query.addValues({
1785 truster_id: req.current_member_id,
1786 trustee_id: trustee_id
1787 });
1788 if (unit_id && !area_id && !issue_id) {
1790 // check privilege
1791 requireUnitPrivilege(conn, req, res, unit_id, function() {
1793 query.addKeys(['unit_id'])
1794 query.addValues({ unit_id: unit_id, scope: 'unit' });
1795 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1796 });
1798 } else if (!unit_id && area_id && !issue_id) {
1800 // check privilege
1801 requireAreaPrivilege(conn, req, res, area_id, function() {
1803 query.addKeys(['area_id'])
1804 query.addValues({ area_id: area_id, scope: 'area' });
1805 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1806 });
1808 } else if (!unit_id && !area_id && issue_id) {
1810 // check privilege
1811 requireIssuePrivilege(conn, req, res, issue_id, function() {
1813 // check issue state
1814 requireIssueState(conn, req, res, issue_id, ['admission', 'discussion', 'verification', 'voting'], function() {
1816 query.addKeys(['issue_id'])
1817 query.addValues({ issue_id: issue_id, scope: 'issue' });
1818 db.query(conn, req, res, query, function(result) { respond('json', conn, req, res, 'ok'); });
1819 });
1820 });
1821 } else {
1822 respond('json', conn, req, res, 'unprocessable', null, 'Excactly one of unit_id, area_id, issue_id must be supplied!');
1823 return;
1827 });
1829 });
1830 },
1832 };

Impressum / About Us