1 | /**
|
2 | * vim:set sw=2 ts=2 sts=2 ft=javascript expandtab:
|
3 | *
|
4 | * # Database Module
|
5 | *
|
6 | * ## License
|
7 | *
|
8 | * Licensed to the Apache Software Foundation (ASF) under one
|
9 | * or more contributor license agreements. See the NOTICE file
|
10 | * distributed with this work for additional information
|
11 | * regarding copyright ownership. The ASF licenses this file
|
12 | * to you under the Apache License, Version 2.0 (the
|
13 | * "License"); you may not use this file except in compliance
|
14 | * with the License. You may obtain a copy of the License at
|
15 | *
|
16 | * http://www.apache.org/licenses/LICENSE-2.0
|
17 | *
|
18 | * Unless required by applicable law or agreed to in writing,
|
19 | * software distributed under the License is distributed on an
|
20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
21 | * KIND, either express or implied. See the License for the
|
22 | * specific language governing permissions and limitations
|
23 | * under the License.
|
24 | *
|
25 | * ## Description
|
26 | *
|
27 | * This module consists only on a wrapper around etherpad database.
|
28 | */
|
29 |
|
30 | module.exports = (function () {
|
31 | ;
|
32 | // Dependencies
|
33 | var ld = require('lodash');
|
34 |
|
35 | var storage = {};
|
36 |
|
37 | // Database PREFIX CONSTANTS
|
38 | storage.DBPREFIX = { GLOBAL: 'mypads:' };
|
39 | var DBPG = storage.DBPREFIX.GLOBAL;
|
40 | storage.DBPREFIX.CONF = DBPG + 'conf:';
|
41 | storage.DBPREFIX.USER = DBPG + 'user:';
|
42 | storage.DBPREFIX.GROUP = DBPG + 'group:';
|
43 | storage.DBPREFIX.PAD = DBPG + 'pad:';
|
44 | storage.DBPREFIX.JOBQ = DBPG + 'jobqueue:';
|
45 |
|
46 | try {
|
47 | // Normal case : when installed as a plugin
|
48 | storage.db = require('ep_etherpad-lite/node/db/DB').db;
|
49 | }
|
50 | catch (e) {
|
51 | // Testing case : we need to mock the database connection, using ueberDB and
|
52 | // coherent default configuration with eptherpad-lite one.
|
53 | var ueberDB = require('ueberdb2');
|
54 | storage.db = new ueberDB.database('dirty', { filename: './test.db' });
|
55 | storage.db.init(function (err) { if (err) { console.error(err); } });
|
56 | }
|
57 |
|
58 | /**
|
59 | * `init` function for initial configuration cache init and in memory
|
60 | * secondary indexes. At the moment only user / logins / firstname / lastname.
|
61 | */
|
62 |
|
63 | storage.init = function (callback) {
|
64 | var configuration = require('./configuration.js');
|
65 | configuration.init(function (err) {
|
66 | if (err) { return callback(err); }
|
67 | var userCache = require('./model/user-cache.js');
|
68 | userCache.init(callback);
|
69 | });
|
70 | };
|
71 |
|
72 | /**
|
73 | * ## Internal functions `fn`
|
74 | *
|
75 | * These functions are not private like with closures, for testing purposes,
|
76 | * but they are expected be used only internally by other MyPads functions.
|
77 | */
|
78 |
|
79 | storage.fn = {};
|
80 |
|
81 | /** ### getDelKeys
|
82 | *
|
83 | * `getDelKeys` is a function for multiple asynchronous gets and removes,
|
84 | * taking :
|
85 | *
|
86 | * - a `del` boolean, for removals to *true*
|
87 | * - a `keys` array, wich contains a list of keys to retrieve
|
88 | * - a `callback` function, called if error or when finished with *null* and
|
89 | * the `results` object, which is composed of keys and values for gets,
|
90 | * *true* for removals
|
91 | * FIXME: TCO ?
|
92 | */
|
93 |
|
94 | storage.fn.getDelKeys = function (del, keys, callback) {
|
95 | var done;
|
96 | var results = del ? true : {};
|
97 | var action = del ? 'remove' : 'get';
|
98 | var getDel = function (k) {
|
99 | storage.db[action](k, function (err, res) {
|
100 | if (err) { return callback(err); }
|
101 | if (!del && !ld.isNull(res)) { results[k] = res; }
|
102 | done();
|
103 | });
|
104 | };
|
105 | done = function () {
|
106 | if (keys.length) {
|
107 | getDel(keys.pop());
|
108 | } else {
|
109 | return callback(null, results);
|
110 | }
|
111 | };
|
112 | done();
|
113 | };
|
114 |
|
115 | /**
|
116 | * ### getKeysUncached
|
117 | *
|
118 | * Same than `getKeys` but tries to get values in bulk, bypassing UeberDB cache
|
119 | */
|
120 | storage.fn.getKeysUncached = function (keys, callback) {
|
121 | if (storage.db && (storage.db.type === 'postgres' || storage.db.type === 'postgrespool')) {
|
122 | var query = 'SELECT key, value FROM store WHERE key = ANY ($1)';
|
123 | return storage.db.db.wrappedDB.db.query(query, [keys], function (err, queryResult) {
|
124 | if (err) { return callback(err); }
|
125 |
|
126 | var rows = queryResult.rows;
|
127 |
|
128 | var results = {};
|
129 | rows.forEach(function (row) {
|
130 | try {
|
131 | if (!ld.isNull(row)) { results[row.key] = JSON.parse(row.value); }
|
132 | } catch (e) {
|
133 | console.error('JSON-PROBLEM:' + row.value);
|
134 | }
|
135 | });
|
136 |
|
137 | return callback(null, results);
|
138 | });
|
139 | }
|
140 |
|
141 | return storage.fn.getKeys(keys, callback);
|
142 | };
|
143 |
|
144 | /**
|
145 | * ### getKeys
|
146 | *
|
147 | * `getKeys` is an helper around `storage.fn.getDelKeys` with `del` argument
|
148 | * to *false*.
|
149 | */
|
150 | storage.fn.getKeys = ld.partial(storage.fn.getDelKeys, false);
|
151 |
|
152 | /**
|
153 | * ### delKeys
|
154 | *
|
155 | * `delKeys` is an helper around `storage.fn.getDelKeys` with `del` argument
|
156 | * to *true*.
|
157 | */
|
158 |
|
159 | storage.fn.delKeys = ld.partial(storage.fn.getDelKeys, true);
|
160 |
|
161 | /**
|
162 | * `setKeys` is a function for multiple asynchronous sets, taking :
|
163 | s
|
164 | * - a `kv` object, wich contains a list a keys and values to set
|
165 | * - a `callback` function, called if error or when finished with null
|
166 | * FIXME: TCO ?
|
167 | */
|
168 |
|
169 | storage.fn.setKeys = function (kv, callback) {
|
170 | var done;
|
171 | var pairs = ld.pairs(kv);
|
172 | var setK = function (k, v) { storage.db.set(k, v, done); };
|
173 | done = function (err) {
|
174 | if (err) { return callback(err); }
|
175 | if (pairs.length) {
|
176 | var pair = pairs.pop();
|
177 | setK(pair[0], pair[1]);
|
178 | } else {
|
179 | return callback(null);
|
180 | }
|
181 | };
|
182 | done();
|
183 | };
|
184 |
|
185 | /**
|
186 | * `setKeysIfNotExists` is a function for multiple asynchronous sets if the
|
187 | * key does not exists in database, taking :
|
188 | s
|
189 | * - a `kv` object, wich contains a list a keys and values to set
|
190 | * - a `callback` function, called if error or when finished with null
|
191 | * FIXME: TCO ?
|
192 | */
|
193 |
|
194 | storage.fn.setKeysIfNotExists = function (kv, callback) {
|
195 | var done;
|
196 | var pairs = ld.pairs(kv);
|
197 | var setK = function (k, v) { storage.db.set(k, v, done); };
|
198 | done = function (err) {
|
199 | if (err) { return callback(err); }
|
200 | if (pairs.length) {
|
201 | var pair = pairs.pop();
|
202 | storage.db.get(pair[0], function(err, res) {
|
203 | if (err) { return callback(err); }
|
204 | if (ld.isUndefined(res) || ld.isNull(res)) {
|
205 | setK(pair[0], pair[1]);
|
206 | } else {
|
207 | done();
|
208 | }
|
209 | });
|
210 | } else {
|
211 | return callback(null);
|
212 | }
|
213 | };
|
214 | done();
|
215 | };
|
216 |
|
217 | return storage;
|
218 | }).call(this);
|