atlassian-connect-express
Version:
Library for building Atlassian Add-ons on top of Express
240 lines (220 loc) • 5.87 kB
JavaScript
const _ = require("lodash");
const MongoClient = require("mongodb").MongoClient;
const CONNECTOR_OPTIONS = [
"rs",
"url",
"logging",
"collection",
"database",
"adapter"
];
class MongoDbAdapter {
constructor(logger, opts) {
const self = this;
_.bindAll(
self,
"get",
"set",
"del",
"isMemoryStore",
"getAllClientInfos",
"_get",
"_mongoDbConnected"
);
self.mongoDbClient = null;
self.db = null;
self.collection = null;
self.settings = {
// Backwards compatible way of determining connectionUrl
connectionUrl:
process.env["MONGODB_URI"] ||
process.env["DB_URL"] ||
opts.rs ||
opts.url,
collectionName: opts.collection || "AddonSettings",
databaseName: opts.database || undefined, // undefined means default to the one from connectionUrl
// Prepare mongodb options
mongoDbOpts: _.assign(
{
retryWrites: false,
useNewUrlParser: true,
promiseLibrary: Promise
},
opts
)
};
// Eliminate all options in opts for self class and pass along the rest to mongoclient
CONNECTOR_OPTIONS.forEach(
(optName => {
delete self.settings.mongoDbOpts[optName];
}).bind(self)
);
// Add logger
if (opts.logging) {
self.settings.mongoDbOpts.logger = logger;
}
self.connectionPromise = MongoClient.connect(
self.settings.connectionUrl,
self.settings.mongoDbOpts
)
.then(self._mongoDbConnected)
.catch(err => {
(logger || console).error(
`Could not establish MongoDB database connection: ${err.toString()}`
);
return Promise.reject(err);
});
}
isMemoryStore() {
// Even if the mongoDB server was operating in in-memory mode, it’s not in the node process memory and won’t
// disappear when the node service restarts, so from our perspective it’s still not an in-memory store
return false;
}
_mongoDbConnected(mongoDbClient) {
const self = this;
self.mongoDbClient = mongoDbClient;
self.db = self.mongoDbClient.db(self.settings.databaseName);
self.collection = self.db.collection(self.settings.collectionName);
return Promise.all([
self.collection.createIndex({ key: 1, clientKey: 1 }, { unique: true }),
self.collection.createIndex({ key: 1 })
]);
}
// run a query with an arbitrary 'where' clause
// returns an array of values
_get(where) {
const self = this;
return self.connectionPromise
.then(() => {
return self.collection.find(where).toArray();
})
.then(resultsArray => {
if (
!resultsArray ||
!Array.isArray(resultsArray) ||
resultsArray.length === 0
) {
return [];
}
return resultsArray.map(entry => {
return entry.val;
});
});
}
getAllClientInfos() {
const self = this;
return self._get({ key: "clientInfo" });
}
// return a promise to a single object identified by 'key' in the data belonging to tenant 'clientKey'
get(key, clientKey) {
const self = this;
if (typeof key !== "string") {
return Promise.reject(
new Error("The key for what to get in MongoDB must be a string")
);
}
if (typeof clientKey !== "string") {
return Promise.reject(
new Error("The clientKey for what to get in MongoDB must be a string")
);
}
return self.connectionPromise
.then(() => {
return self.collection.findOne({
key,
clientKey
});
})
.then(resultDoc => {
if (resultDoc) {
return resultDoc.val;
} else {
return null;
}
});
}
set(key, value, clientKey) {
const self = this;
if (typeof key !== "string") {
return Promise.reject(
new Error("The key for what to set in MongoDB must be a string")
);
}
if (typeof clientKey !== "string") {
return Promise.reject(
new Error("The clientKey for what to set in MongoDB must be a string")
);
}
value = getAsObject(value);
return self.connectionPromise
.then(() => {
return self.collection.replaceOne(
{
key,
clientKey
},
{
key,
clientKey,
val: value
},
{
upsert: true
}
);
})
.then(() => {
return value;
});
}
del(key, clientKey) {
const self = this;
let query;
if (clientKey === undefined) {
// try to interpret key as clientKey
query = {
clientKey: key
};
} else {
query = {
key,
clientKey
};
}
// Type checks
if (typeof query.clientKey !== "string") {
return Promise.reject(
new Error(
"The clientKey for what to delete from MongoDB must be a string"
)
);
}
if (query.key !== undefined && typeof query.key !== "string") {
return Promise.reject(
new Error(
"The key for what to delete from MongoDB must be a string (or undefined if you " +
"want to delete all entries with the given clientKey)"
)
);
}
return self.connectionPromise.then(() => {
return self.collection.deleteMany(query);
});
}
}
function getAsObject(val) {
if (typeof val === "string") {
try {
val = JSON.parse(val);
} catch (e) {
// it's OK if we can't parse this. We'll just return the string below.
}
}
return val;
}
module.exports = function (logger, opts) {
if (0 === arguments.length) {
return MongoDbAdapter;
}
return new MongoDbAdapter(logger, opts);
};