lfapi

view lfapi/main.js @ 1:9fe872cc376d

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

Impressum / About Us