-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for MySQL storage #31
Merged
Merged
Changes from 2 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
215a8bd
add support for mysql/mariadb
datvong-wm e8f3f48
rename dao-factory name; add more logging; fix some configs
datvong-wm 8c89c96
add additional fields to package.json. add more comments
datvong-wm 2b4ea1a
remove CLS usage
datvong-wm 5632aab
add comments
datvong-wm 63b6619
add comments; add mysql to travis
datvong-wm f0a7f24
fix bad yarn.lock file
datvong-wm 7d3bd88
remove package-lock file
datvong-wm 792de2d
fix travis.yml file
datvong-wm 4b417ce
prettier-ify files
datvong-wm 32a2dd0
prettier-ify another file
datvong-wm 7a6ab2a
code cleanup
datvong-wm 6545b4a
add eslint
datvong-wm 5a2eeed
fix rollback - id from copied package should not be copied
datvong-wm 2d448f0
query should sort Package by created_ time
datvong-wm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
electrode-ota-server-dao-factory | ||
=== | ||
This project is part of the [electrode-ota-server](https://github.com/electrode-io/electrode-ota-server) | ||
|
||
It is not meant to be used as a standalone module, use at your own risk. | ||
|
||
This service provides an abstraction layer for the DAO data store. It allows for pluggable datastore driver used to power DAO storage. | ||
Currently support datastores are MariaDB (electrode-ota-server-dao-mariadb) and Cassandra (electrode-ota-server-dao-cassandra). | ||
|
||
## Install | ||
``` | ||
$ npm install electrode-ota-server-dao-factory | ||
``` | ||
|
||
## Usage | ||
|
||
- Install this module in your package.json. | ||
- Update your config to use this package, specify a driver. | ||
- Add the driver and driver options. | ||
|
||
In this sample configuration, we are using the MariaDB driver to save DAO instances. | ||
``` | ||
{ | ||
"electrode-ota-server-dao-factory": { | ||
"options": { | ||
"driver": "electrode-ota-server-dao-mariadb" | ||
} | ||
}, | ||
"electrode-ota-server-dao-mariadb": { | ||
"options": { | ||
host: '127.0.0.1', | ||
user: 'root', | ||
password: 'root' | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "electrode-ota-server-dao-factory", | ||
"version": "3.0.0", | ||
"description": "OTA DAO", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "ota-mocha", | ||
"build": "ota-babel", | ||
"coverage": "ota-nyc", | ||
"prepublish": "npm run build" | ||
}, | ||
"dependencies": { | ||
"electrode-ota-server-diregister": "^2.1.0-beta.7" | ||
}, | ||
"devDependencies": { | ||
"electrode-ota-server-util-dev": "^2.1.0-beta.7", | ||
"bluebird": "^3.5.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
import {alreadyExistsMsg} from 'electrode-ota-server-errors'; | ||
import {promiseMap, reducer, toJSON} from 'electrode-ota-server-util'; | ||
import _ from "lodash"; | ||
|
||
const isEmpty = (arr) => { | ||
if (arr == null) return true; | ||
if (arr.length === 0) return true; | ||
return false; | ||
}; | ||
const isNotEmpty = (arr) => !isEmpty(arr); | ||
|
||
const apply = (target, source) => { | ||
if (!source) { | ||
return target; | ||
} | ||
for (const key of Object.keys(target)) { | ||
if (key == '_validators') continue; | ||
if (source.hasOwnProperty(key)) { | ||
const newValue = source[key]; | ||
target[key] = newValue; | ||
} | ||
} | ||
return target; | ||
}; | ||
|
||
const ACCESSKEY = ["name", "id", "expires", "description", "lastAccess", "createdTime", "createdBy", "friendlyName"]; | ||
|
||
const nullUnlessValue = (obj, keys) => { | ||
for (const key of keys) { | ||
if (obj[key] === void(0)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you want to null properties that do not exist also? don't like I think |
||
obj[key] = null; | ||
} | ||
} | ||
return obj; | ||
}; | ||
|
||
const historySort = history => history && history.sort((a, b) => b.created_.getTime() - a.created_.getTime()); | ||
|
||
/** | ||
* DAO Factory methods to save/update/delete DAOs to/from DataStore. | ||
* | ||
* Models | ||
* - App | ||
* - Deployment | ||
* - Package | ||
* - User | ||
* | ||
* Model functions | ||
* - deleteAsync() | ||
* - findOneAsync(query, options) | ||
* - findAsync(query, options) | ||
* - saveAsync(options) | ||
* - updateAsync(updates, options) | ||
*/ | ||
export default class DaoFactory { | ||
constructor({driver, logger}) { | ||
this.driver = driver; | ||
this.logger = logger; | ||
} | ||
async init() { | ||
return this.driver.init(); | ||
} | ||
|
||
async createUser({email, name, accessKeys, linkedProviders = []}) { | ||
const user = new this.driver.User({email, name, accessKeys, linkedProviders}); | ||
const exists = await user.saveAsync(); | ||
alreadyExistsMsg(exists, `User already exists ${email}`); | ||
|
||
return user; | ||
} | ||
|
||
userById(id) { | ||
return this.driver.User.findOneAsync({id}); | ||
} | ||
|
||
userByAccessKey(accessKeys) { | ||
return this.driver.User.findOneAsync({accessKeys}); | ||
} | ||
|
||
async userByEmail(email) { | ||
return this.driver.User.findOneAsync({email}); | ||
|
||
} | ||
|
||
async updateUser(currentEmail, update) { | ||
const user = await this.userByEmail(currentEmail); | ||
apply(user, update); | ||
await user.saveAsync(); | ||
const js = toJSON(user); | ||
|
||
for (const key of Object.keys(js.accessKeys)) { | ||
nullUnlessValue(js.accessKeys[key], ACCESSKEY); | ||
} | ||
return js; | ||
|
||
} | ||
|
||
/** | ||
* Create a new App | ||
* | ||
* @param {object} values | ||
* name: name of app, string | ||
* deployments: list of deployments | ||
* collaborators: map of collaborators | ||
* | ||
*/ | ||
async createApp({name, deployments={}, collaborators}) { | ||
const app = new this.driver.App({name, collaborators}); | ||
const savedApp = await app.saveAsync(); | ||
|
||
const deps = Object.keys(deployments); | ||
if (isNotEmpty(deps)) { | ||
for (const name of deps) { | ||
await this.addDeployment(app.id, name, deployments[name]); | ||
} | ||
} | ||
savedApp.deployments = deps; | ||
return savedApp; | ||
} | ||
|
||
async removeApp(appId) { | ||
const app = await this.appById(appId); | ||
if (isEmpty(app)) return; | ||
return app.deleteAsync(); | ||
} | ||
|
||
async updateApp(id, {name, collaborators}) { | ||
const app = await this.appById(id); | ||
app.name = name; | ||
app.collaborators = collaborators; | ||
const resp = await app.saveAsync(); | ||
return app; | ||
} | ||
|
||
appById(id) { | ||
return this.driver.App.findOneAsync({id}); | ||
} | ||
|
||
appsForCollaborator(email) { | ||
return this.driver.App.findAsync({collaborators: email}); | ||
} | ||
|
||
appForCollaborator(email, appName) { | ||
return this.driver.App.findOneAsync({collaborators: email, name: appName}); | ||
} | ||
|
||
addDeployment(app, name, {key}) { | ||
const deployment = new this.driver.Deployment({name, key, AppId:app}); | ||
return deployment.saveAsync(); | ||
} | ||
|
||
async removeDeployment(appId, deploymentName) { | ||
const deployment = await this.driver.Deployment.findOneAsync({AppId:appId, name:deploymentName}); | ||
return deployment.deleteAsync(); | ||
} | ||
|
||
async renameDeployment(appId, oldName, newName) { | ||
const deployment = await this.driver.Deployment.findOneAsync({AppId:appId, name:oldName}); | ||
deployment.name = newName; | ||
return deployment.saveAsync(); | ||
} | ||
|
||
async deploymentForKey(deploymentKey) { | ||
let dep = await this.driver.Deployment.findOneAsync({key: deploymentKey}); | ||
if (dep && isNotEmpty(dep.history_)) { | ||
dep = toJSON(dep); | ||
dep.package = await this.driver.Package.findOneAsync({id_: dep.history_[0]}); | ||
return dep; | ||
} | ||
return dep; | ||
} | ||
|
||
async deploymentsByApp(appId, deployments) { | ||
if (isEmpty(deployments)) { | ||
return []; | ||
} | ||
const deps = await this.driver.Deployment.findAsync({appId, name:deployments}); | ||
if (isEmpty(deps)) return []; | ||
return promiseMap(reducer(deps, (ret, d) => (ret[d.name] = this.deploymentForKey(d.key)))); | ||
} | ||
|
||
async deploymentByApp(appId, deployment) { | ||
const d = await this.driver.Deployment.findOneAsync({appId, name: deployment}); | ||
if (!d) return; | ||
return this.deploymentForKey(d.key); | ||
} | ||
|
||
async _historyForDeployment(key) { | ||
const res = await this.driver.Deployment.findOneAsync({key}); | ||
return res.history_; | ||
} | ||
|
||
async addPackage(deploymentKey, value) { | ||
|
||
const deployment = await this.driver.Deployment.findOneAsync({key:deploymentKey}); | ||
if (isEmpty(deployment)) { | ||
throw new Error(`Can not find deployment ${deploymentKey}.`); | ||
} | ||
const pkg = new this.driver.Package(value); | ||
await pkg.saveAsync(); | ||
await deployment.associateAsync(pkg); | ||
|
||
return pkg; | ||
} | ||
|
||
async updatePackage(deploymentKey, pkg, label) { | ||
const history_ = await this._historyForDeployment(deploymentKey); | ||
if (isEmpty(history_)) { | ||
throw new Error(`Can not update a package without history, probably means things have gone awry.`); | ||
} | ||
let rpkg; | ||
|
||
if (label) { | ||
rpkg = await this.driver.Package.findOneAsync({id_: history_, label}); | ||
} else { | ||
rpkg = await this.driver.Package.findOneAsync({id_: history_}); | ||
} | ||
await rpkg.updateAsync(pkg); | ||
return rpkg; | ||
|
||
} | ||
|
||
async history(appId, deploymentName) { | ||
const deployment = await this.deploymentByApp(appId, deploymentName); | ||
if (!deployment || !deployment.history_) { | ||
return []; | ||
} | ||
const pkgs = await this.driver.Package.findAsync({id_: deployment.history_}); | ||
|
||
return historySort(pkgs); | ||
} | ||
|
||
async historyByIds(historyIds) { | ||
if (historyIds == null || historyIds.length == 0) { | ||
return []; | ||
} | ||
const pkgs = await this.driver.Package.findAsync({id_: historyIds}); | ||
return historySort(pkgs); | ||
} | ||
|
||
async clearHistory(appId, deploymentName) { | ||
const deployment = await this.deploymentByApp(appId, deploymentName); | ||
if (deployment && isNotEmpty(deployment.history_)) { | ||
return this.driver.Package.delete({id_: deployment.history_}); | ||
} | ||
} | ||
|
||
async historyLabel(appId, deploymentName, label) { | ||
const deployment = await this.deploymentByApp(appId, deploymentName); | ||
if (!deployment || isEmpty(deployment.history_)) { | ||
return; | ||
} | ||
const pkg = await this.driver.Package.findOneAsync({id_: deployment.history_, label: label}); | ||
return pkg; | ||
} | ||
|
||
async packageById(pkgId) { | ||
if (!pkgId) return; | ||
return this.driver.Package.findOneAsync({id_:pkgId}); | ||
} | ||
|
||
async upload(packageHash, content) { | ||
if (!Buffer.isBuffer(content)) { | ||
content = Buffer.from(content, 'utf8') | ||
} | ||
const pkg = new this.driver.PackageContent({packageHash, content}); | ||
const exists = await pkg.saveAsync(); | ||
return exists; | ||
} | ||
|
||
async download(packageHash) { | ||
const pkg = await this.driver.PackageContent.findOneAsync({packageHash}); | ||
if (pkg != null) { | ||
return pkg.content; | ||
} | ||
} | ||
|
||
metrics(deploymentKey) { | ||
return this.driver.Metric.findAsync({deploymentKey}); | ||
} | ||
|
||
/** | ||
* Record download or deploy metric | ||
* | ||
* @param {object} values | ||
* appVersion text, | ||
* deploymentKey text, | ||
* clientUniqueId text, | ||
* label text | ||
* status text, | ||
* previousLabelOrAppVersion text, | ||
* previousDeploymentKey text, | ||
* | ||
* "appVersion": "1.0.0", | ||
* "deploymentKey": "5UfjnOxv1FnCJ_DwPqMWQYSVlp0H4yecGHaB-", | ||
* "clientUniqueId": "fe231438a4f62c70", | ||
* "label": "v1", | ||
* "status": "DeploymentSucceeded", | ||
* "previousLabelOrAppVersion": "1.0.0", | ||
* "previousDeploymentKey": "5UfjnOxv1FnCJ_DwPqMWQYSVlp0H4yecGHaB-" | ||
*/ | ||
async insertMetric(values) { | ||
const metric = new this.driver.Metric(values); | ||
return metric.saveAsync(); | ||
} | ||
|
||
clientRatio(clientUniqueId, packageHash) { | ||
return this.driver.ClientRatio.findOneAsync({clientUniqueId, packageHash}); | ||
} | ||
|
||
/** | ||
* Tracks whether we updated the client or not last time. | ||
* @param {string} clientUniqueId : Client Device Unique ID | ||
* @param {string} packageHash : Package hash to update to. | ||
* @param {float} ratio : Deployment ratio | ||
* @param {bool} updated : flag indicating if client was/was not updated. | ||
*/ | ||
insertClientRatio(clientUniqueId, packageHash, ratio, updated) { | ||
const clientRatio = new this.driver.ClientRatio({clientUniqueId, packageHash, ratio, updated}); | ||
return clientRatio.saveAsync(); | ||
} | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add author, repository, license, bugs