1 | var config = require('../lib/config')();
|
2 | var pg = require('pg');
|
3 | require('../validators');
|
4 | var validate = require('validate.js');
|
5 | var errors = require('../errors');
|
6 | var squel = require('squel').useFlavour('postgres');
|
7 | var helpers = require('../helpers');
|
8 | var moment = require('moment');
|
9 | var union = require('lodash.union');
|
10 | var changes = {};
|
11 |
|
12 | module.exports = changes;
|
13 | var pgURL = config.PostgresURL;
|
14 |
|
15 | changes.get = function(from, to, users, tags, bbox, callback) {
|
16 | var parseError = validateParams({'from': from, 'to': to});
|
17 | if (parseError) {
|
18 | return callback(new errors.ParseError(parseError));
|
19 | }
|
20 |
|
21 | getQuery(from, to, users, tags, bbox, function(err, query, usersData) {
|
22 | if (err) {
|
23 | callback(err, null);
|
24 | return;
|
25 | }
|
26 | pg.connect(pgURL, function(err, client, done) {
|
27 | if (err) {
|
28 | callback(err, null);
|
29 | return;
|
30 | }
|
31 | console.log(query);
|
32 | client.query(query, function(err, result) {
|
33 | done();
|
34 | if (err) {
|
35 | return callback(err, null);
|
36 | }
|
37 | if (result.rows.length === 0) {
|
38 | return callback(new errors.NotFoundError('No records found'));
|
39 | }
|
40 |
|
41 | var reducerFn = function(memo, row) {
|
42 | var hour = moment.utc(row.change_at).startOf('hour').toISOString();
|
43 | if (!memo.hasOwnProperty(hour)) {
|
44 | memo[hour] = {};
|
45 | }
|
46 | var username = row.username;
|
47 | if (!memo[hour].hasOwnProperty(username)) {
|
48 | memo[hour][username] = {};
|
49 | }
|
50 | var thisUserMemo = memo[hour][username];
|
51 | ['nodes', 'ways', 'relations'].forEach(function(thing) {
|
52 | if (!thisUserMemo.hasOwnProperty(thing)) {
|
53 | thisUserMemo[thing] = {
|
54 | 'c': 0,
|
55 | 'm': 0,
|
56 | 'd': 0
|
57 | };
|
58 | }
|
59 | ['c', 'm', 'd'].forEach(function(type) {
|
60 |
|
61 | thisUserMemo[thing][type] += row[thing][type];
|
62 | });
|
63 | });
|
64 | ['tags_created', 'tags_modified', 'tags_deleted'].forEach(function(thing) {
|
65 | if (!thisUserMemo.hasOwnProperty(thing)) {
|
66 | thisUserMemo[thing] = {};
|
67 | }
|
68 | Object.keys(row[thing]).forEach(function(tag) {
|
69 | if (!thisUserMemo[thing].hasOwnProperty(tag)) {
|
70 | thisUserMemo[thing][tag] = {};
|
71 | }
|
72 | Object.keys(row[thing][tag]).forEach(function(value) {
|
73 | if (!thisUserMemo[thing][tag].hasOwnProperty(value)) {
|
74 | thisUserMemo[thing][tag][value] = 0;
|
75 | }
|
76 | thisUserMemo[thing][tag][value] += row[thing][tag][value];
|
77 | });
|
78 |
|
79 | });
|
80 | });
|
81 |
|
82 | thisUserMemo.changesets = union(thisUserMemo.changesets, row.changesets);
|
83 | return memo;
|
84 | };
|
85 |
|
86 | callback(null, result.rows.reduce(reducerFn, {}));
|
87 | });
|
88 | });
|
89 | });
|
90 | };
|
91 |
|
92 | function validateParams(params) {
|
93 | var constraints = {
|
94 | 'from': {
|
95 | 'presence': true,
|
96 | 'datetime': true
|
97 | },
|
98 | 'to': {
|
99 | 'presence': true,
|
100 | 'datetime': true
|
101 | }
|
102 | };
|
103 | var errs = validate(params, constraints);
|
104 | if (errs) {
|
105 | var errMsg = Object.keys(errs).map(function(key) {
|
106 | return errs[key][0];
|
107 | }).join(', ');
|
108 | return errMsg;
|
109 | }
|
110 | return null;
|
111 | }
|
112 |
|
113 | function getQuery(from, to, users, tags, bbox, callback) {
|
114 |
|
115 | var sql = squel.select({'parameterCharacter': '!!'})
|
116 | .from('stats')
|
117 | .where('change_at > !!', from)
|
118 | .where('change_at < !!', to);
|
119 |
|
120 | if (bbox) {
|
121 | var polygonGeojson = JSON.stringify(helpers.getPolygon(bbox).geometry);
|
122 | var changesetSql = squel.select({'parameterCharacter': '!!'})
|
123 | .field('array_agg(id)')
|
124 | .from('changesets')
|
125 | .where('created_at > !!', from)
|
126 | .where('created_at < !!', to)
|
127 | .where('ST_Intersects(changesets.bbox, ST_SetSRID(ST_GeomFromGeoJSON(!!), 4326))', polygonGeojson);
|
128 | sql = sql.where('changesets <@ !!', changesetSql);
|
129 | }
|
130 |
|
131 | if (tags) {
|
132 | var tagsArray = tags.split(',').map(function(tag) {
|
133 | return tag;
|
134 | });
|
135 | var tagSql = prepareTagQuery(tagsArray, sql);
|
136 | sql = sql.where(tagSql);
|
137 | }
|
138 |
|
139 | if (users) {
|
140 | var usersArray = users.split(',').map(function(user) {
|
141 | return user;
|
142 | });
|
143 |
|
144 | getUserIds(usersArray, function(err, usersData) {
|
145 | if (err) {
|
146 | return callback(err);
|
147 | }
|
148 |
|
149 | var userIds = [];
|
150 | usersData.rows.forEach(function (r) {
|
151 | userIds.push(r.id);
|
152 | });
|
153 |
|
154 | sql.where('uid in !!', userIds);
|
155 | callback(null, sql.toParam(), usersData);
|
156 | });
|
157 | } else {
|
158 | sql.field('u.name', 'username')
|
159 | .field('stats.id', 'id')
|
160 | .field('uid')
|
161 | .field('change_at')
|
162 | .field('nodes')
|
163 | .field('ways')
|
164 | .field('relations')
|
165 | .field('tags_created')
|
166 | .field('tags_modified')
|
167 | .field('tags_deleted')
|
168 | .field('changesets')
|
169 | .join('users', 'u', 'u.id = stats.uid');
|
170 | console.log('#sql', sql.toString());
|
171 | callback(null, sql.toParam());
|
172 | }
|
173 |
|
174 |
|
175 | }
|
176 |
|
177 | function getUserIds(users, callback) {
|
178 | var userSql = squel.select({'parameterCharacter': '!!'})
|
179 | .from('users')
|
180 | .field('id')
|
181 | .field('name')
|
182 | .where('name in !!', users);
|
183 |
|
184 | pg.connect(pgURL, function(err, client, done) {
|
185 | if (err) {
|
186 | return callback(err, null);
|
187 | }
|
188 | client.query(userSql.toParam(), function(err, result) {
|
189 | done();
|
190 | if (err) {
|
191 | return callback(err);
|
192 | }
|
193 |
|
194 | if (result.rows.length === 0) {
|
195 | return callback(new errors.NotFoundError('No such users'));
|
196 | }
|
197 | callback(null, result);
|
198 |
|
199 | });
|
200 | });
|
201 | }
|
202 |
|
203 | function prepareTagQuery(tags, sql) {
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | var tagsSql = squel.expr();
|
209 | tagsSql.options.parameterCharacter = '!!';
|
210 |
|
211 | tags.forEach(function (tag) {
|
212 | var key = tag.split('=')[0];
|
213 | var value = tag.split('=')[1];
|
214 | if (value !== '*') {
|
215 | tagsSql.or('tags_created -> !! ? !!', key, value);
|
216 | tagsSql.or('tags_modified -> !! ? !!', key, value);
|
217 | tagsSql.or('tags_deleted -> !! ? !!', key, value);
|
218 | } else {
|
219 | tagsSql.or('tags_created ? !!', key);
|
220 | tagsSql.or('tags_modified ? !!', key);
|
221 | tagsSql.or('tags_deleted ? !!', key);
|
222 | }
|
223 | });
|
224 | return tagsSql;
|
225 | }
|