UNPKG

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