Merge branch 'dashboard'
This commit is contained in:
commit
1dae3019c4
50 changed files with 9552 additions and 57 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -71,7 +71,9 @@ desktop.ini
|
|||
|
||||
# Compiled files
|
||||
/dist
|
||||
/dist-frontend
|
||||
|
||||
# Misc
|
||||
/convert.js
|
||||
/startscript.js
|
||||
/.cache
|
||||
|
|
8
dashboard/.babelrc
Normal file
8
dashboard/.babelrc
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"plugins": [
|
||||
["transform-runtime", {
|
||||
"regenerator": true
|
||||
}],
|
||||
"transform-object-rest-spread"
|
||||
]
|
||||
}
|
3
dashboard/.gitignore
vendored
Normal file
3
dashboard/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.cache
|
||||
/dist
|
||||
/node_modules
|
7668
dashboard/package-lock.json
generated
Normal file
7668
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
dashboard/package.json
Normal file
27
dashboard/package.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "zeppelin-dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "rimraf dist && parcel build src/index.html --out-dir dist",
|
||||
"watch": "parcel src/index.html"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/component-compiler-utils": "^3.0.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-cookie": "^2.2.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-hot-reload-api": "^2.3.3",
|
||||
"vue-router": "^3.0.6"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
]
|
||||
}
|
53
dashboard/src/api.ts
Normal file
53
dashboard/src/api.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
const apiUrl = process.env.API_URL;
|
||||
|
||||
type QueryParamObject = { [key: string]: string | null };
|
||||
|
||||
function buildQueryString(params: QueryParamObject) {
|
||||
if (Object.keys(params).length === 0) return "";
|
||||
|
||||
return (
|
||||
"?" +
|
||||
Array.from(Object.entries(params))
|
||||
.map(pair => `${encodeURIComponent(pair[0])}=${encodeURIComponent(pair[1] || "")}`)
|
||||
.join("&")
|
||||
);
|
||||
}
|
||||
|
||||
let apiKey = null;
|
||||
export function setApiKey(newKey) {
|
||||
apiKey = newKey;
|
||||
}
|
||||
export function hasApiKey() {
|
||||
return apiKey != null;
|
||||
}
|
||||
export function resetApiKey() {
|
||||
apiKey = null;
|
||||
}
|
||||
|
||||
export function request(resource, fetchOpts: RequestInit = {}) {
|
||||
return fetch(`${apiUrl}/${resource}`, fetchOpts)
|
||||
.then(res => res.json())
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function get(resource: string, params: QueryParamObject = {}) {
|
||||
const headers: Record<string, string> = apiKey ? { "X-Api-Key": (apiKey as unknown) as string } : {};
|
||||
return request(resource + buildQueryString(params), {
|
||||
method: "GET",
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
export function post(resource: string, params: QueryParamObject = {}) {
|
||||
const headers: Record<string, string> = apiKey ? { "X-Api-Key": (apiKey as unknown) as string } : {};
|
||||
return request(resource + buildQueryString(params), {
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
15
dashboard/src/auth.ts
Normal file
15
dashboard/src/auth.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { NavigationGuard } from "vue-router";
|
||||
import { RootStore } from "./store";
|
||||
|
||||
export const authGuard: NavigationGuard = async (to, from, next) => {
|
||||
if (RootStore.state.auth.apiKey) return next(); // We have an API key -> authenticated
|
||||
if (RootStore.state.auth.loadedInitialAuth) return next("/login"); // No API key and initial auth data was already loaded -> not authenticated
|
||||
await RootStore.dispatch("auth/loadInitialAuth"); // Initial auth data wasn't loaded yet (per above check) -> load it now
|
||||
if (RootStore.state.auth.apiKey) return next();
|
||||
next("/login"); // Still no API key -> not authenticated
|
||||
};
|
||||
|
||||
export const loginCallbackGuard: NavigationGuard = async (to, from, next) => {
|
||||
await RootStore.dispatch("auth/setApiKey", to.query.apiKey);
|
||||
next("/dashboard");
|
||||
};
|
3
dashboard/src/components/App.vue
Normal file
3
dashboard/src/components/App.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
36
dashboard/src/components/Dashboard.vue
Normal file
36
dashboard/src/components/Dashboard.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div v-if="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1>Guilds</h1>
|
||||
<table v-for="guild in guilds">
|
||||
<tr>
|
||||
<td>{{ guild.id }}</td>
|
||||
<td>{{ guild.name }}</td>
|
||||
<td>
|
||||
<a v-bind:href="'/dashboard/guilds/' + guild.id + '/config'">Config</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import {LoadStatus} from "../store/types";
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
this.$store.dispatch("guilds/loadAvailableGuilds");
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$state.guilds.availableGuildsLoadStatus !== LoadStatus.Done;
|
||||
},
|
||||
...mapState({
|
||||
guilds: 'guilds/available',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
11
dashboard/src/components/Index.vue
Normal file
11
dashboard/src/components/Index.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<p>Redirecting...</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.$router.push('/login');
|
||||
}
|
||||
};
|
||||
</script>
|
14
dashboard/src/components/Login.vue
Normal file
14
dashboard/src/components/Login.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<a v-bind:href="env.API_URL + '/auth/login'">Login</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
computed: {
|
||||
blah() {
|
||||
return this.$state.apiKey;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
15
dashboard/src/index.html
Normal file
15
dashboard/src/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Zeppelin Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
31
dashboard/src/main.ts
Normal file
31
dashboard/src/main.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Vue from "vue";
|
||||
import { RootStore } from "./store";
|
||||
import { router } from "./routes";
|
||||
|
||||
get("/foo", { bar: "baz" });
|
||||
|
||||
// Set up a read-only global variable to access specific env vars
|
||||
Vue.mixin({
|
||||
data() {
|
||||
return {
|
||||
get env() {
|
||||
return Object.freeze({
|
||||
API_URL: process.env.API_URL,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
import App from "./components/App.vue";
|
||||
import Login from "./components/Login.vue";
|
||||
import { get } from "./api";
|
||||
|
||||
const app = new Vue({
|
||||
router,
|
||||
store: RootStore,
|
||||
el: "#app",
|
||||
render(h) {
|
||||
return h(App);
|
||||
},
|
||||
});
|
27
dashboard/src/routes.ts
Normal file
27
dashboard/src/routes.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Vue from "vue";
|
||||
import VueRouter, { RouteConfig } from "vue-router";
|
||||
import Index from "./components/Index.vue";
|
||||
import Login from "./components/Login.vue";
|
||||
import LoginCallback from "./components/LoginCallback.vue";
|
||||
import Dashboard from "./components/Dashboard.vue";
|
||||
import store from "./store";
|
||||
import { authGuard, loginCallbackGuard } from "./auth";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const publicRoutes: RouteConfig[] = [
|
||||
{ path: "/", component: Index },
|
||||
{ path: "/login", component: Login },
|
||||
{ path: "/login-callback", beforeEnter: loginCallbackGuard },
|
||||
];
|
||||
|
||||
const authenticatedRoutes: RouteConfig[] = [{ path: "/dashboard", component: Dashboard }];
|
||||
|
||||
authenticatedRoutes.forEach(route => {
|
||||
route.beforeEnter = authGuard;
|
||||
});
|
||||
|
||||
export const router = new VueRouter({
|
||||
mode: "history",
|
||||
routes: [...publicRoutes, ...authenticatedRoutes],
|
||||
});
|
46
dashboard/src/store/auth.ts
Normal file
46
dashboard/src/store/auth.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { get, hasApiKey, post, setApiKey } from "../api";
|
||||
import { ActionTree, Module } from "vuex";
|
||||
import { AuthState, RootState } from "./types";
|
||||
|
||||
export const AuthStore: Module<AuthState, RootState> = {
|
||||
namespaced: true,
|
||||
|
||||
state: {
|
||||
apiKey: null,
|
||||
loadedInitialAuth: false,
|
||||
},
|
||||
|
||||
actions: {
|
||||
async loadInitialAuth({ dispatch, commit, state }) {
|
||||
if (state.loadedInitialAuth) return;
|
||||
|
||||
const storedKey = localStorage.getItem("apiKey");
|
||||
if (storedKey) {
|
||||
console.log("key?", storedKey);
|
||||
const result = await post("auth/validate-key", { key: storedKey });
|
||||
if (result.isValid) {
|
||||
await dispatch("setApiKey", storedKey);
|
||||
} else {
|
||||
localStorage.removeItem("apiKey");
|
||||
}
|
||||
}
|
||||
|
||||
commit("markInitialAuthLoaded");
|
||||
},
|
||||
|
||||
setApiKey({ commit, state }, newKey: string) {
|
||||
localStorage.setItem("apiKey", newKey);
|
||||
commit("setApiKey", newKey);
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
setApiKey(state: AuthState, key) {
|
||||
state.apiKey = key;
|
||||
},
|
||||
|
||||
markInitialAuthLoaded(state: AuthState) {
|
||||
state.loadedInitialAuth = true;
|
||||
},
|
||||
},
|
||||
};
|
34
dashboard/src/store/guilds.ts
Normal file
34
dashboard/src/store/guilds.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { get } from "../api";
|
||||
import { Module } from "vuex";
|
||||
import { GuildState, LoadStatus, RootState } from "./types";
|
||||
|
||||
export const GuildStore: Module<GuildState, RootState> = {
|
||||
namespaced: true,
|
||||
|
||||
state: {
|
||||
availableGuildsLoadStatus: LoadStatus.None,
|
||||
available: [],
|
||||
configs: {},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async loadAvailableGuilds({ dispatch, commit, state }) {
|
||||
if (state.availableGuildsLoadStatus !== LoadStatus.None) return;
|
||||
commit("setAvailableGuildsLoadStatus", LoadStatus.Loading);
|
||||
|
||||
const availableGuilds = await get("guilds/available");
|
||||
commit("setAvailableGuilds", availableGuilds);
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
setAvailableGuildsLoadStatus(state: GuildState, status: LoadStatus) {
|
||||
state.availableGuildsLoadStatus = status;
|
||||
},
|
||||
|
||||
setAvailableGuilds(state: GuildState, guilds) {
|
||||
state.available = guilds;
|
||||
state.availableGuildsLoadStatus = LoadStatus.Done;
|
||||
},
|
||||
},
|
||||
};
|
28
dashboard/src/store/index.ts
Normal file
28
dashboard/src/store/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Vue from "vue";
|
||||
import Vuex, { Store } from "vuex";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
import { RootState } from "./types";
|
||||
import { AuthStore } from "./auth";
|
||||
import { GuildStore } from "./guilds";
|
||||
|
||||
export const RootStore = new Vuex.Store<RootState>({
|
||||
modules: {
|
||||
auth: AuthStore,
|
||||
guilds: GuildStore,
|
||||
},
|
||||
});
|
||||
|
||||
// Set up typings so Vue/our components know about the state's types
|
||||
declare module "vue/types/options" {
|
||||
interface ComponentOptions<V extends Vue> {
|
||||
store?: Store<RootState>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "vue/types/vue" {
|
||||
interface Vue {
|
||||
$store: Store<RootState>;
|
||||
}
|
||||
}
|
27
dashboard/src/store/types.ts
Normal file
27
dashboard/src/store/types.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
export enum LoadStatus {
|
||||
None = 1,
|
||||
Loading,
|
||||
Done,
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
apiKey: string | null;
|
||||
loadedInitialAuth: boolean;
|
||||
}
|
||||
|
||||
export interface GuildState {
|
||||
availableGuildsLoadStatus: LoadStatus;
|
||||
available: Array<{
|
||||
guild_id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
}>;
|
||||
configs: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type RootState = {
|
||||
auth: AuthState;
|
||||
guilds: GuildState;
|
||||
};
|
4
dashboard/ts-vue-shim.d.ts
vendored
Normal file
4
dashboard/ts-vue-shim.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
28
dashboard/tsconfig.json
Normal file
28
dashboard/tsconfig.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "es2015",
|
||||
"noImplicitAny": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"target": "esnext",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2017",
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"files": [
|
||||
"ts-vue-shim.d.ts"
|
||||
]
|
||||
}
|
5
nodemon-api.json
Normal file
5
nodemon-api.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"watch": "src",
|
||||
"ext": "ts",
|
||||
"exec": "ts-node ./src/api/index.ts"
|
||||
}
|
563
package-lock.json
generated
563
package-lock.json
generated
|
@ -1834,6 +1834,55 @@
|
|||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
|
||||
"integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
|
||||
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-GmK8AKu8i+s+EChK/uZ5IbrXPcPaQKWaNSGevDT/7o3gFObwSUQwqb1jMqxuo+YPvj0ckGzINI+EO7EHcmJjKg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz",
|
||||
"integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/express-serve-static-core": {
|
||||
"version": "4.16.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.6.tgz",
|
||||
"integrity": "sha512-8wr3CA/EMybyb6/V8qvTRKiNkPmgUA26uA9XWD6hlA0yFDuqi4r2L0C2B0U2HAYltJamoYJszlkaWM31vrKsHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz",
|
||||
|
@ -1858,20 +1907,29 @@
|
|||
"@types/lodash": {
|
||||
"version": "4.14.110",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.110.tgz",
|
||||
"integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw=="
|
||||
"integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash.at": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.at/-/lodash.at-4.6.3.tgz",
|
||||
"integrity": "sha512-I9uzVJxGEvHRTsXI/1AUjkaJOxWdYRE5pOoUUJxfbq5N4y071Y2CyGDrxmNwfYJosCmX3jF+2Zhrw7A6pQc7pg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
|
||||
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/moment-timezone": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.6.tgz",
|
||||
"integrity": "sha512-rMZjLmXs9sly1UbwxckyAEvQkrwrGqR24nFAjFrndRJBBnUooCCD0LPmdRcf9haHXFnckI9E3ko0oC6LEDk7dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"moment": ">=2.14.0"
|
||||
}
|
||||
|
@ -1881,6 +1939,51 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz",
|
||||
"integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ=="
|
||||
},
|
||||
"@types/oauth": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz",
|
||||
"integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/passport": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
|
||||
"integrity": "sha512-R2FXqM+AgsMIym0PuKj08Ybx+GR6d2rU3b1/8OcHolJ+4ga2pRPX105wboV6hq1AJvMo2frQzYKdqXS5+4cyMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"@types/passport-oauth2": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.8.tgz",
|
||||
"integrity": "sha512-tlX16wyFE5YJR2pHpZ308dgB1MV9/Ra2wfQh71eWk+/umPoD1Rca2D4N5M27W7nZm1wqUNGTk1I864nHvEgiFA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/oauth": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express-serve-static-core": "*",
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
|
@ -1911,6 +2014,15 @@
|
|||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
|
||||
|
@ -2058,6 +2170,11 @@
|
|||
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
|
||||
"dev": true
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
|
@ -2376,6 +2493,11 @@
|
|||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
|
||||
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
|
||||
},
|
||||
"base64url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
||||
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
|
@ -2404,6 +2526,38 @@
|
|||
"integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=",
|
||||
"dev": true
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"boxen": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
|
||||
|
@ -2521,6 +2675,11 @@
|
|||
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
|
||||
"dev": true
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"cache-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||
|
@ -2867,6 +3026,19 @@
|
|||
"xdg-basedir": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
|
||||
|
@ -2876,6 +3048,16 @@
|
|||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"copy-descriptor": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||
|
@ -2887,6 +3069,15 @@
|
|||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.0.tgz",
|
||||
|
@ -3117,6 +3308,16 @@
|
|||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"detect-newline": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
|
||||
|
@ -3187,6 +3388,11 @@
|
|||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.116",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz",
|
||||
|
@ -3204,6 +3410,11 @@
|
|||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.1.tgz",
|
||||
"integrity": "sha512-cjx7oFbFIyZMpmWaEBnKeJXWAVzjXwK6yHiz/5X73A2Ww4pnabw+4ZaA/MxLroIQQrB3dL6XzEz8P3aZsSdj8Q=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
|
@ -3213,8 +3424,9 @@
|
|||
}
|
||||
},
|
||||
"eris": {
|
||||
"version": "github:abalabahaha/eris#0509d444813d49c320be3b38fd2e05f7d4ae2078",
|
||||
"from": "github:abalabahaha/eris#dev",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eris/-/eris-0.10.0.tgz",
|
||||
"integrity": "sha512-xAvmD4wsE5mwuiP+wy8RiarjiuwCylSsglKqru4J4sk0/WGOnSOfEZf43YLx/TcF4J1D4B2VMTq38446Bk1x1Q==",
|
||||
"requires": {
|
||||
"opusscript": "^0.0.4",
|
||||
"tweetnacl": "^1.0.0",
|
||||
|
@ -3255,6 +3467,11 @@
|
|||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
|
@ -3305,6 +3522,11 @@
|
|||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"dev": true
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"event-stream": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
|
@ -3485,6 +3707,58 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.0.tgz",
|
||||
"integrity": "sha512-1Z7/t3Z5ZnBG252gKUPyItc4xdeaA0X934ca2ewckAsVsw9EG71i++ZHZPYnus8g/s5Bty8IMpSVEuRkmwwPRQ==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
@ -3656,6 +3930,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"find-parent-dir": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz",
|
||||
|
@ -3699,6 +3997,11 @@
|
|||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fragment-cache": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
|
||||
|
@ -3708,6 +4011,11 @@
|
|||
"map-cache": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"from": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||
|
@ -4542,6 +4850,18 @@
|
|||
"whatwg-encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
|
@ -4651,7 +4971,6 @@
|
|||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
|
@ -4738,6 +5057,11 @@
|
|||
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
|
||||
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
},
|
||||
"is-accessor-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||
|
@ -7777,11 +8101,6 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||
"integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw="
|
||||
},
|
||||
"lodash.has": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
|
||||
"integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI="
|
||||
},
|
||||
"lodash.intersection": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.intersection/-/lodash.intersection-4.4.0.tgz",
|
||||
|
@ -7797,6 +8116,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
|
||||
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ=="
|
||||
},
|
||||
"lodash.pick": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM="
|
||||
},
|
||||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
|
@ -7920,6 +8244,11 @@
|
|||
"escape-string-regexp": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"mem": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
|
||||
|
@ -7928,6 +8257,11 @@
|
|||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
|
||||
|
@ -7937,6 +8271,11 @@
|
|||
"readable-stream": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
|
@ -7958,17 +8297,20 @@
|
|||
"to-regex": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
|
@ -8098,6 +8440,11 @@
|
|||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
|
||||
"dev": true
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz",
|
||||
|
@ -8221,6 +8568,11 @@
|
|||
"integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==",
|
||||
"dev": true
|
||||
},
|
||||
"oauth": {
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
"integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
|
@ -8297,6 +8649,14 @@
|
|||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@ -8488,12 +8848,51 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"pascalcase": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
|
||||
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
||||
"dev": true
|
||||
},
|
||||
"passport": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz",
|
||||
"integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=",
|
||||
"requires": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1"
|
||||
}
|
||||
},
|
||||
"passport-custom": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.0.5.tgz",
|
||||
"integrity": "sha1-LR2cF0pqRoW/Aom85hCRzV7HsPQ=",
|
||||
"requires": {
|
||||
"passport-strategy": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-oauth2": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz",
|
||||
"integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==",
|
||||
"requires": {
|
||||
"base64url": "3.x.x",
|
||||
"oauth": "0.9.x",
|
||||
"passport-strategy": "1.x.x",
|
||||
"uid2": "0.0.x",
|
||||
"utils-merge": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-strategy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
|
||||
},
|
||||
"path-dirname": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
|
||||
|
@ -8527,6 +8926,11 @@
|
|||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"path-type": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
|
||||
|
@ -8536,6 +8940,11 @@
|
|||
"pify": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"pause": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
|
||||
},
|
||||
"pause-stream": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||
|
@ -8737,6 +9146,15 @@
|
|||
"integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==",
|
||||
"dev": true
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
|
||||
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.0"
|
||||
}
|
||||
},
|
||||
"ps-tree": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
|
||||
|
@ -8786,6 +9204,22 @@
|
|||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
|
@ -9199,8 +9633,7 @@
|
|||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sane": {
|
||||
"version": "4.1.0",
|
||||
|
@ -9283,6 +9716,59 @@
|
|||
"semver": "^5.0.3"
|
||||
}
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
|
@ -9317,6 +9803,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
|
@ -9651,6 +10142,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
|
@ -9898,6 +10394,11 @@
|
|||
"repeat-string": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"toposort": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
|
||||
|
@ -10034,6 +10535,15 @@
|
|||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typeorm": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.14.tgz",
|
||||
|
@ -10094,6 +10604,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"uid2": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
|
||||
"integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
|
||||
},
|
||||
"undefsafe": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz",
|
||||
|
@ -10186,6 +10701,11 @@
|
|||
"crypto-random-string": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
@ -10303,6 +10823,11 @@
|
|||
"object.getownpropertydescriptors": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
|
@ -10326,6 +10851,11 @@
|
|||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
|
@ -10337,6 +10867,11 @@
|
|||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
|
||||
"integrity": "sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg=="
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
||||
|
|
42
package.json
42
package.json
|
@ -4,54 +4,64 @@
|
|||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "jest src",
|
||||
"start-dev": "ts-node src/index.ts",
|
||||
"start-watch": "nodemon",
|
||||
"format": "prettier --write \"./**/*.ts\"",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
|
||||
"start-bot-dev": "ts-node src/index.ts",
|
||||
"start-bot-prod": "cross-env NODE_ENV=production node dist/index.js",
|
||||
"watch-bot": "nodemon --config nodemon-bot.json",
|
||||
"build": "rimraf dist && tsc",
|
||||
"start-prod": "cross-env NODE_ENV=production node dist/index.js",
|
||||
"migrate": "npm run typeorm -- migration:run"
|
||||
"start-api-dev": "ts-node src/api/index.ts",
|
||||
"start-api-prod": "cross-env NODE_ENV=production node dist/api/index.js",
|
||||
"watch-api": "nodemon --config nodemon-api.json",
|
||||
"format": "prettier --write \"./src/**/*.ts\"",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
|
||||
"migrate": "npm run typeorm -- migration:run",
|
||||
"migrate-rollback": "npm run typeorm -- migration:revert",
|
||||
"test": "jest src"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/lodash.at": "^4.6.3",
|
||||
"@types/moment-timezone": "^0.5.6",
|
||||
"ajv": "^6.7.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"dotenv": "^4.0.0",
|
||||
"emoji-regex": "^7.0.1",
|
||||
"eris": "github:abalabahaha/eris#dev",
|
||||
"eris": "^0.10.0",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"express": "^4.17.0",
|
||||
"humanize-duration": "^3.15.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"knub": "^20.1.0",
|
||||
"last-commit-log": "^2.1.0",
|
||||
"lodash.at": "^4.6.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.has": "^4.5.2",
|
||||
"lodash.intersection": "^4.4.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.pick": "^4.4.0",
|
||||
"moment-timezone": "^0.5.21",
|
||||
"mysql": "^2.16.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-custom": "^1.0.5",
|
||||
"passport-oauth2": "^1.5.0",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"tlds": "^1.203.1",
|
||||
"tmp": "0.0.33",
|
||||
"ts-node": "^3.3.0",
|
||||
"typeorm": "^0.2.14",
|
||||
"typescript": "^3.3.3333",
|
||||
"uuid": "^3.3.2"
|
||||
"uuid": "^3.3.2",
|
||||
"vuex": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@types/cors": "^2.8.5",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/jest": "^24.0.11",
|
||||
"@types/lodash.at": "^4.6.3",
|
||||
"@types/moment-timezone": "^0.5.6",
|
||||
"@types/node": "^10.12.0",
|
||||
"@types/passport": "^1.0.0",
|
||||
"@types/passport-oauth2": "^1.4.8",
|
||||
"@types/tmp": "0.0.33",
|
||||
"babel-jest": "^24.5.0",
|
||||
"husky": "^1.3.1",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"name": "zeppelin",
|
||||
"script": "npm",
|
||||
"args": "run start-prod",
|
||||
"args": "run start-bot-prod",
|
||||
"log_date_format": "YYYY-MM-DD HH:mm:ss"
|
||||
}
|
||||
]
|
||||
|
|
119
src/api/auth.ts
Normal file
119
src/api/auth.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import passport from "passport";
|
||||
import OAuth2Strategy from "passport-oauth2";
|
||||
import CustomStrategy from "passport-custom";
|
||||
import { DashboardLogins, DashboardLoginUserData } from "../data/DashboardLogins";
|
||||
import pick from "lodash.pick";
|
||||
import https from "https";
|
||||
|
||||
const DISCORD_API_URL = "https://discordapp.com/api";
|
||||
|
||||
function simpleDiscordAPIRequest(bearerToken, path): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.get(
|
||||
`${DISCORD_API_URL}/${path}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${bearerToken}`,
|
||||
},
|
||||
},
|
||||
res => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`Discord API error ${res.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
res.on("data", data => resolve(JSON.parse(data)));
|
||||
},
|
||||
);
|
||||
|
||||
request.on("error", err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
export function initAuth(app: express.Express) {
|
||||
app.use(passport.initialize());
|
||||
|
||||
if (!process.env.CLIENT_ID) {
|
||||
throw new Error("Auth: CLIENT ID missing");
|
||||
}
|
||||
|
||||
if (!process.env.CLIENT_SECRET) {
|
||||
throw new Error("Auth: CLIENT SECRET missing");
|
||||
}
|
||||
|
||||
if (!process.env.OAUTH_CALLBACK_URL) {
|
||||
throw new Error("Auth: OAUTH CALLBACK URL missing");
|
||||
}
|
||||
|
||||
if (!process.env.DASHBOARD_URL) {
|
||||
throw new Error("DASHBOARD_URL missing!");
|
||||
}
|
||||
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((user, done) => done(null, user));
|
||||
|
||||
const dashboardLogins = new DashboardLogins();
|
||||
|
||||
// Initialize API tokens
|
||||
passport.use(
|
||||
"api-token",
|
||||
new CustomStrategy(async (req, cb) => {
|
||||
console.log("in api-token strategy");
|
||||
const apiKey = req.header("X-Api-Key");
|
||||
if (!apiKey) return cb();
|
||||
|
||||
const userId = await dashboardLogins.getUserIdByApiKey(apiKey);
|
||||
if (userId) {
|
||||
cb(null, { userId });
|
||||
}
|
||||
|
||||
cb();
|
||||
}),
|
||||
);
|
||||
|
||||
// Initialize OAuth2 for Discord login
|
||||
passport.use(
|
||||
new OAuth2Strategy(
|
||||
{
|
||||
authorizationURL: "https://discordapp.com/api/oauth2/authorize",
|
||||
tokenURL: "https://discordapp.com/api/oauth2/token",
|
||||
clientID: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
callbackURL: process.env.OAUTH_CALLBACK_URL,
|
||||
scope: ["identify"],
|
||||
},
|
||||
async (accessToken, refreshToken, profile, cb) => {
|
||||
const user = await simpleDiscordAPIRequest(accessToken, "users/@me");
|
||||
const userData = pick(user, ["username", "discriminator", "avatar"]) as DashboardLoginUserData;
|
||||
const apiKey = await dashboardLogins.addLogin(user.id, userData);
|
||||
// TODO: Revoke access token, we don't need it anymore
|
||||
console.log("done, calling cb with", apiKey);
|
||||
cb(null, { apiKey });
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
app.get("/auth/login", passport.authenticate("oauth2"));
|
||||
app.get(
|
||||
"/auth/oauth-callback",
|
||||
passport.authenticate("oauth2", { failureRedirect: "/", session: false }),
|
||||
(req, res) => {
|
||||
console.log("redirecting to a non-existent page haHAA");
|
||||
res.redirect(`${process.env.DASHBOARD_URL}/login-callback/?apiKey=${req.user.apiKey}`);
|
||||
},
|
||||
);
|
||||
app.post("/auth/validate-key", async (req: Request, res: Response) => {
|
||||
const key = req.params.key || req.query.key;
|
||||
if (!key) {
|
||||
return res.status(400).json({ error: "No key supplied" });
|
||||
}
|
||||
|
||||
const userId = await dashboardLogins.getUserIdByApiKey(key);
|
||||
if (!userId) {
|
||||
return res.status(403).json({ error: "Invalid key" });
|
||||
}
|
||||
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
}
|
15
src/api/guilds.ts
Normal file
15
src/api/guilds.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import express from "express";
|
||||
import passport from "passport";
|
||||
import { AllowedGuilds } from "../data/AllowedGuilds";
|
||||
|
||||
export function initGuildsAPI(app: express.Express) {
|
||||
const guildAPIRouter = express.Router();
|
||||
guildAPIRouter.use(passport.authenticate("api-token"));
|
||||
|
||||
const allowedGuilds = new AllowedGuilds();
|
||||
|
||||
guildAPIRouter.get("/guilds/available", async (req, res) => {
|
||||
const guilds = await allowedGuilds.getForDashboardUser(req.user.userId);
|
||||
res.end(guilds);
|
||||
});
|
||||
}
|
29
src/api/index.ts
Normal file
29
src/api/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
require("dotenv").config();
|
||||
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { initAuth } from "./auth";
|
||||
import { initGuildsAPI } from "./guilds";
|
||||
import { connect } from "../data/db";
|
||||
|
||||
console.log("Connecting to database...");
|
||||
connect().then(() => {
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.DASHBOARD_URL,
|
||||
}),
|
||||
);
|
||||
app.use(express.json());
|
||||
|
||||
initAuth(app);
|
||||
initGuildsAPI(app);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.end({ status: "cookies" });
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => console.log(`API server listening on port ${port}`));
|
||||
});
|
41
src/data/AllowedGuilds.ts
Normal file
41
src/data/AllowedGuilds.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { AllowedGuild } from "./entities/AllowedGuild";
|
||||
import {
|
||||
getConnection,
|
||||
getRepository,
|
||||
Repository,
|
||||
Transaction,
|
||||
TransactionManager,
|
||||
TransactionRepository,
|
||||
} from "typeorm";
|
||||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export class AllowedGuilds extends BaseRepository {
|
||||
private allowedGuilds: Repository<AllowedGuild>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.allowedGuilds = getRepository(AllowedGuild);
|
||||
}
|
||||
|
||||
async isAllowed(guildId) {
|
||||
const count = await this.allowedGuilds.count({
|
||||
where: {
|
||||
guild_id: guildId,
|
||||
},
|
||||
});
|
||||
return count !== 0;
|
||||
}
|
||||
|
||||
getForDashboardUser(userId) {
|
||||
return this.allowedGuilds
|
||||
.createQueryBuilder("allowed_guilds")
|
||||
.innerJoin(
|
||||
"dashboard_users",
|
||||
"dashboard_users",
|
||||
"dashboard_users.guild_id = allowed_guilds.guild_id AND dashboard_users.user_id = :userId",
|
||||
{ userId },
|
||||
)
|
||||
.getMany();
|
||||
}
|
||||
}
|
|
@ -7,5 +7,5 @@ export enum CaseTypes {
|
|||
Mute,
|
||||
Unmute,
|
||||
Expunged,
|
||||
Softban
|
||||
Softban,
|
||||
}
|
||||
|
|
49
src/data/Configs.ts
Normal file
49
src/data/Configs.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { Config } from "./entities/Config";
|
||||
import {
|
||||
getConnection,
|
||||
getRepository,
|
||||
Repository,
|
||||
Transaction,
|
||||
TransactionManager,
|
||||
TransactionRepository,
|
||||
} from "typeorm";
|
||||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { connection } from "./db";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export class Configs extends BaseRepository {
|
||||
private configs: Repository<Config>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.configs = getRepository(Config);
|
||||
}
|
||||
|
||||
getActiveByKey(key) {
|
||||
return this.configs.findOne({
|
||||
where: {
|
||||
key,
|
||||
is_active: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async hasConfig(key) {
|
||||
return (await this.getActiveByKey(key)) != null;
|
||||
}
|
||||
|
||||
async saveNewRevision(key, config, editedBy) {
|
||||
return connection.transaction(async entityManager => {
|
||||
const repo = entityManager.getRepository(Config);
|
||||
// Mark all old revisions inactive
|
||||
await repo.update({ key }, { is_active: false });
|
||||
// Add new, active revision
|
||||
await repo.insert({
|
||||
key,
|
||||
config,
|
||||
is_active: true,
|
||||
edited_by: editedBy,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
85
src/data/DashboardLogins.ts
Normal file
85
src/data/DashboardLogins.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { getRepository, Repository } from "typeorm";
|
||||
import { DashboardLogin } from "./entities/DashboardLogin";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import crypto from "crypto";
|
||||
import moment from "moment-timezone";
|
||||
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import uuidv4 from "uuid/v4";
|
||||
import { DBDateFormat } from "../utils";
|
||||
import { log } from "util";
|
||||
|
||||
export interface DashboardLoginUserData {
|
||||
username: string;
|
||||
discriminator: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export class DashboardLogins extends BaseRepository {
|
||||
private dashboardLogins: Repository<DashboardLogin>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.dashboardLogins = getRepository(DashboardLogin);
|
||||
}
|
||||
|
||||
async getUserIdByApiKey(apiKey: string): Promise<string | null> {
|
||||
const [loginId, token] = apiKey.split(".");
|
||||
if (!loginId || !token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const login = await this.dashboardLogins
|
||||
.createQueryBuilder()
|
||||
.where("id = :id", { id: loginId })
|
||||
.andWhere("expires_at > NOW()")
|
||||
.getOne();
|
||||
|
||||
if (!login) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hash = crypto.createHash("sha256");
|
||||
hash.update(loginId + token); // Remember to use loginId as the salt
|
||||
const hashedToken = hash.digest("hex");
|
||||
if (hashedToken !== login.token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return login.user_id;
|
||||
}
|
||||
|
||||
async addLogin(userId: string, userData: DashboardLoginUserData): Promise<string> {
|
||||
// Generate random login id
|
||||
let loginId;
|
||||
while (true) {
|
||||
loginId = uuidv4();
|
||||
const existing = await this.dashboardLogins.findOne({
|
||||
where: {
|
||||
id: loginId,
|
||||
},
|
||||
});
|
||||
if (!existing) break;
|
||||
}
|
||||
|
||||
// Generate token
|
||||
const token = uuidv4();
|
||||
const hash = crypto.createHash("sha256");
|
||||
hash.update(loginId + token); // Use loginId as a salt
|
||||
const hashedToken = hash.digest("hex");
|
||||
|
||||
// Save this to the DB
|
||||
await this.dashboardLogins.insert({
|
||||
id: loginId,
|
||||
token: hashedToken,
|
||||
user_id: userId,
|
||||
user_data: userData,
|
||||
logged_in_at: moment().format(DBDateFormat),
|
||||
expires_at: moment()
|
||||
.add(1, "day")
|
||||
.format(DBDateFormat),
|
||||
});
|
||||
|
||||
return `${loginId}.${token}`;
|
||||
}
|
||||
}
|
6
src/data/DashboardRoles.ts
Normal file
6
src/data/DashboardRoles.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export enum DashboardRoles {
|
||||
Viewer = 1,
|
||||
Editor,
|
||||
Manager,
|
||||
ServerOwner,
|
||||
}
|
12
src/data/DashboardUsers.ts
Normal file
12
src/data/DashboardUsers.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { getRepository, Repository } from "typeorm";
|
||||
import { DashboardUser } from "./entities/DashboardUser";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export class DashboardUsers extends BaseRepository {
|
||||
private dashboardUsers: Repository<DashboardUser>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.dashboardUsers = getRepository(DashboardUser);
|
||||
}
|
||||
}
|
|
@ -201,7 +201,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
|
|||
const deleted = await this.messages
|
||||
.createQueryBuilder()
|
||||
.where("id IN (:ids)", { ids })
|
||||
.where("deleted_at = :deletedAt", { deletedAt })
|
||||
.andWhere("deleted_at = :deletedAt", { deletedAt })
|
||||
.getMany();
|
||||
|
||||
if (deleted.length) {
|
||||
|
|
14
src/data/entities/AllowedGuild.ts
Normal file
14
src/data/entities/AllowedGuild.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Entity, Column, PrimaryColumn, CreateDateColumn } from "typeorm";
|
||||
|
||||
@Entity("allowed_guilds")
|
||||
export class AllowedGuild {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
icon: string;
|
||||
}
|
23
src/data/entities/Config.ts
Normal file
23
src/data/entities/Config.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Entity, Column, PrimaryColumn, CreateDateColumn } from "typeorm";
|
||||
|
||||
@Entity("configs")
|
||||
export class Config {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
key: string;
|
||||
|
||||
@Column()
|
||||
config: string;
|
||||
|
||||
@Column()
|
||||
is_active: boolean;
|
||||
|
||||
@Column()
|
||||
edited_by: string;
|
||||
|
||||
@Column()
|
||||
edited_at: string;
|
||||
}
|
24
src/data/entities/DashboardLogin.ts
Normal file
24
src/data/entities/DashboardLogin.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
import { DashboardLoginUserData } from "../DashboardLogins";
|
||||
|
||||
@Entity("dashboard_logins")
|
||||
export class DashboardLogin {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
token: string;
|
||||
|
||||
@Column()
|
||||
user_id: string;
|
||||
|
||||
@Column("simple-json")
|
||||
user_data: DashboardLoginUserData;
|
||||
|
||||
@Column()
|
||||
logged_in_at: string;
|
||||
|
||||
@Column()
|
||||
expires_at: string;
|
||||
}
|
18
src/data/entities/DashboardUser.ts
Normal file
18
src/data/entities/DashboardUser.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("dashboard_users")
|
||||
export class DashboardUser {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
user_id: string;
|
||||
|
||||
@Column()
|
||||
username: string;
|
||||
|
||||
@Column()
|
||||
role: string;
|
||||
}
|
32
src/index.ts
32
src/index.ts
|
@ -10,6 +10,8 @@ import { SimpleError } from "./SimpleError";
|
|||
import DiscordRESTError from "eris/lib/errors/DiscordRESTError"; // tslint:disable-line
|
||||
import DiscordHTTPError from "eris/lib/errors/DiscordHTTPError"; // tslint:disable-line
|
||||
|
||||
import { Configs } from "./data/Configs";
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
// Error handling
|
||||
|
@ -71,8 +73,8 @@ import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
|||
import { customArgumentTypes } from "./customArgumentTypes";
|
||||
import { errorMessage, successMessage } from "./utils";
|
||||
import { startUptimeCounter } from "./uptime";
|
||||
import { AllowedGuilds } from "./data/AllowedGuilds";
|
||||
|
||||
// Run latest database migrations
|
||||
logger.info("Connecting to database");
|
||||
connect().then(async conn => {
|
||||
const client = new Client(`Bot ${process.env.TOKEN}`, {
|
||||
|
@ -87,18 +89,25 @@ connect().then(async conn => {
|
|||
}
|
||||
});
|
||||
|
||||
const allowedGuilds = new AllowedGuilds();
|
||||
const guildConfigs = new Configs();
|
||||
|
||||
const bot = new Knub(client, {
|
||||
plugins: availablePlugins,
|
||||
globalPlugins: availableGlobalPlugins,
|
||||
|
||||
options: {
|
||||
canLoadGuild(guildId): Promise<boolean> {
|
||||
return allowedGuilds.isAllowed(guildId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Plugins are enabled if they...
|
||||
* - are base plugins, i.e. always enabled, or
|
||||
* - are dependencies of other enabled plugins, or
|
||||
* - are explicitly enabled in the guild config
|
||||
*/
|
||||
getEnabledPlugins(guildId, guildConfig): string[] {
|
||||
async getEnabledPlugins(guildId, guildConfig): Promise<string[]> {
|
||||
const configuredPlugins = guildConfig.plugins || {};
|
||||
const pluginNames: string[] = Array.from(this.plugins.keys());
|
||||
const plugins: Array<typeof Plugin> = Array.from(this.plugins.values());
|
||||
|
@ -125,22 +134,15 @@ connect().then(async conn => {
|
|||
return Array.from(finalEnabledPlugins.values());
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the requested config file from the config dir
|
||||
* TODO: Move to the database
|
||||
*/
|
||||
async getConfig(id) {
|
||||
const configFile = id ? `${id}.yml` : "global.yml";
|
||||
const configPath = path.join("config", configFile);
|
||||
|
||||
try {
|
||||
await fsp.access(configPath);
|
||||
} catch (e) {
|
||||
return {};
|
||||
const key = id === "global" ? "global" : `guild-${id}`;
|
||||
const row = await guildConfigs.getActiveByKey(key);
|
||||
if (row) {
|
||||
return yaml.safeLoad(row.config);
|
||||
}
|
||||
|
||||
const yamlString = await fsp.readFile(configPath, { encoding: "utf8" });
|
||||
return yaml.safeLoad(yamlString);
|
||||
logger.warn(`No config with key "${key}"`);
|
||||
return {};
|
||||
},
|
||||
|
||||
logFn: (level, msg) => {
|
||||
|
|
39
src/migrateConfigsToDB.ts
Normal file
39
src/migrateConfigsToDB.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { connect } from "./data/db";
|
||||
import { Configs } from "./data/Configs";
|
||||
import path from "path";
|
||||
import * as _fs from "fs";
|
||||
const fs = _fs.promises;
|
||||
|
||||
const authorId = process.argv[2];
|
||||
if (!authorId) {
|
||||
console.error("No author id specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Connecting to database");
|
||||
connect().then(async () => {
|
||||
const configs = new Configs();
|
||||
|
||||
console.log("Loading config files");
|
||||
const configDir = path.join(__dirname, "..", "config");
|
||||
const configFiles = await fs.readdir(configDir);
|
||||
|
||||
console.log("Looping through config files");
|
||||
for (const configFile of configFiles) {
|
||||
const parts = configFile.split(".");
|
||||
const ext = parts[parts.length - 1];
|
||||
if (ext !== "yml") continue;
|
||||
|
||||
const id = parts.slice(0, -1).join(".");
|
||||
const key = id === "global" ? "global" : `guild-${id}`;
|
||||
if (await configs.hasConfig(key)) continue;
|
||||
|
||||
const content = await fs.readFile(path.join(configDir, configFile), { encoding: "utf8" });
|
||||
|
||||
console.log(`Migrating config for ${key}`);
|
||||
await configs.saveNewRevision(key, content, authorId);
|
||||
}
|
||||
|
||||
console.log("Done!");
|
||||
process.exit(0);
|
||||
});
|
54
src/migrations/1558804433320-CreateDashboardLoginsTable.ts
Normal file
54
src/migrations/1558804433320-CreateDashboardLoginsTable.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||
|
||||
export class CreateDashboardLoginsTable1558804433320 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "dashboard_logins",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "varchar",
|
||||
length: "36",
|
||||
isPrimary: true,
|
||||
collation: "ascii_bin",
|
||||
},
|
||||
{
|
||||
name: "token",
|
||||
type: "varchar",
|
||||
length: "64",
|
||||
collation: "ascii_bin",
|
||||
},
|
||||
{
|
||||
name: "user_id",
|
||||
type: "bigint",
|
||||
},
|
||||
{
|
||||
name: "user_data",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "logged_in_at",
|
||||
type: "DATETIME",
|
||||
},
|
||||
{
|
||||
name: "expires_at",
|
||||
type: "DATETIME",
|
||||
},
|
||||
],
|
||||
indices: [
|
||||
{
|
||||
columnNames: ["user_id"],
|
||||
},
|
||||
{
|
||||
columnNames: ["expires_at"],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable("dashboard_logins", true);
|
||||
}
|
||||
}
|
43
src/migrations/1558804449510-CreateDashboardUsersTable.ts
Normal file
43
src/migrations/1558804449510-CreateDashboardUsersTable.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableIndex } from "typeorm";
|
||||
|
||||
export class CreateDashboardUsersTable1558804449510 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "dashboard_users",
|
||||
columns: [
|
||||
{
|
||||
name: "guild_id",
|
||||
type: "bigint",
|
||||
},
|
||||
{
|
||||
name: "user_id",
|
||||
type: "bigint",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
type: "varchar",
|
||||
length: "255",
|
||||
},
|
||||
{
|
||||
name: "role",
|
||||
type: "varchar",
|
||||
length: "32",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createPrimaryKey("dashboard_users", ["guild_id", "user_id"]);
|
||||
await queryRunner.createIndex(
|
||||
"dashboard_users",
|
||||
new TableIndex({
|
||||
columnNames: ["user_id"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable("dashboard_users", true);
|
||||
}
|
||||
}
|
51
src/migrations/1561111990357-CreateConfigsTable.ts
Normal file
51
src/migrations/1561111990357-CreateConfigsTable.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableIndex } from "typeorm";
|
||||
|
||||
export class CreateConfigsTable1561111990357 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "configs",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "int",
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
generationStrategy: "increment",
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
type: "varchar",
|
||||
length: "48",
|
||||
},
|
||||
{
|
||||
name: "config",
|
||||
type: "mediumtext",
|
||||
},
|
||||
{
|
||||
name: "is_active",
|
||||
type: "tinyint",
|
||||
},
|
||||
{
|
||||
name: "edited_by",
|
||||
type: "bigint",
|
||||
},
|
||||
{
|
||||
name: "edited_at",
|
||||
type: "datetime",
|
||||
default: "now()",
|
||||
},
|
||||
],
|
||||
indices: [
|
||||
{
|
||||
columnNames: ["key", "is_active"],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable("configs", true);
|
||||
}
|
||||
}
|
39
src/migrations/1561117545258-CreateAllowedGuildsTable.ts
Normal file
39
src/migrations/1561117545258-CreateAllowedGuildsTable.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||
|
||||
export class CreateAllowedGuildsTable1561117545258 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "allowed_guilds",
|
||||
columns: [
|
||||
{
|
||||
name: "guild_id",
|
||||
type: "bigint",
|
||||
isPrimary: true,
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "varchar",
|
||||
length: "255",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
type: "varchar",
|
||||
length: "255",
|
||||
collation: "ascii_general_ci",
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: "owner_id",
|
||||
type: "bigint",
|
||||
},
|
||||
],
|
||||
indices: [{ columnNames: ["owner_id"] }],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable("allowed_guilds", true);
|
||||
}
|
||||
}
|
190
src/plugins/CustomEvents.ts
Normal file
190
src/plugins/CustomEvents.ts
Normal file
|
@ -0,0 +1,190 @@
|
|||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import { IPluginOptions } from "knub";
|
||||
import { Message, TextChannel, VoiceChannel } from "eris";
|
||||
import { renderTemplate } from "../templateFormatter";
|
||||
import { stripObjectToScalars } from "../utils";
|
||||
import { CasesPlugin } from "./Cases";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
|
||||
// Triggers
|
||||
type CommandTrigger = {
|
||||
type: "command";
|
||||
name: string;
|
||||
params: string;
|
||||
can_use: boolean;
|
||||
};
|
||||
|
||||
type AnyTrigger = CommandTrigger;
|
||||
|
||||
// Actions
|
||||
type AddRoleAction = {
|
||||
type: "add_role";
|
||||
target: string;
|
||||
role: string | string[];
|
||||
};
|
||||
|
||||
type CreateCaseAction = {
|
||||
type: "create_case";
|
||||
case_type: string;
|
||||
mod: string;
|
||||
target: string;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
type MoveToVoiceChannelAction = {
|
||||
type: "move_to_vc";
|
||||
target: string;
|
||||
channel: string;
|
||||
};
|
||||
|
||||
type MessageAction = {
|
||||
type: "message";
|
||||
channel: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
type AnyAction = AddRoleAction | CreateCaseAction | MoveToVoiceChannelAction | MessageAction;
|
||||
|
||||
// Event
|
||||
type CustomEvent = {
|
||||
name: string;
|
||||
trigger: AnyTrigger;
|
||||
actions: AnyAction[];
|
||||
};
|
||||
|
||||
interface ICustomEventsPluginConfig {
|
||||
events: {
|
||||
[key: string]: CustomEvent;
|
||||
};
|
||||
}
|
||||
|
||||
class ActionError extends Error {}
|
||||
|
||||
export class CustomEventsPlugin extends ZeppelinPlugin<ICustomEventsPluginConfig> {
|
||||
public static pluginName = "custom_events";
|
||||
private clearTriggers: () => void;
|
||||
|
||||
public static dependencies = ["cases"];
|
||||
|
||||
getDefaultOptions(): IPluginOptions<ICustomEventsPluginConfig> {
|
||||
return {
|
||||
config: {
|
||||
events: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
for (const [key, event] of Object.entries(this.getConfig().events)) {
|
||||
if (event.trigger.type === "command") {
|
||||
this.commands.add(
|
||||
event.trigger.name,
|
||||
event.trigger.params,
|
||||
(msg, args) => {
|
||||
const strippedMsg = stripObjectToScalars(msg, ["channel", "author"]);
|
||||
this.runEvent(event, { msg, args }, { args, msg: strippedMsg });
|
||||
},
|
||||
{
|
||||
requiredPermission: `events.${key}.trigger.can_use`,
|
||||
locks: [],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUnload() {
|
||||
// TODO: Run this.clearTriggers() once we actually have something there
|
||||
}
|
||||
|
||||
async runEvent(event: CustomEvent, eventData: any, values: any) {
|
||||
try {
|
||||
for (const action of event.actions) {
|
||||
if (action.type === "add_role") {
|
||||
await this.addRoleAction(action, values, event, eventData);
|
||||
} else if (action.type === "create_case") {
|
||||
await this.createCaseAction(action, values, event, eventData);
|
||||
} else if (action.type === "move_to_vc") {
|
||||
await this.moveToVoiceChannelAction(action, values, event, eventData);
|
||||
} else if (action.type === "message") {
|
||||
await this.messageAction(action, values);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ActionError) {
|
||||
if (event.trigger.type === "command") {
|
||||
this.sendErrorMessage((eventData.msg as Message).channel, e.message);
|
||||
} else {
|
||||
// TODO: Where to log action errors from other kinds of triggers?
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async addRoleAction(action: AddRoleAction, values: any, event: CustomEvent, eventData: any) {
|
||||
const targetId = await renderTemplate(action.target, values, false);
|
||||
const target = await this.getMember(targetId);
|
||||
if (!target) throw new ActionError(`Unknown target member: ${targetId}`);
|
||||
|
||||
if (event.trigger.type === "command" && !this.canActOn((eventData.msg as Message).member, target)) {
|
||||
throw new ActionError("Missing permissions");
|
||||
}
|
||||
|
||||
const rolesToAdd = Array.isArray(action.role) ? action.role : [action.role];
|
||||
await target.edit({
|
||||
roles: Array.from(new Set([...target.roles, ...rolesToAdd])),
|
||||
});
|
||||
}
|
||||
|
||||
async createCaseAction(action: CreateCaseAction, values: any, event: CustomEvent, eventData: any) {
|
||||
const modId = await renderTemplate(action.mod, values, false);
|
||||
const targetId = await renderTemplate(action.target, values, false);
|
||||
|
||||
const reason = await renderTemplate(action.reason, values, false);
|
||||
|
||||
if (CaseTypes[action.case_type] == null) {
|
||||
throw new ActionError(`Invalid case type: ${action.type}`);
|
||||
}
|
||||
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
await casesPlugin.createCase({
|
||||
userId: targetId,
|
||||
modId: modId,
|
||||
type: CaseTypes[action.case_type],
|
||||
reason: `__[${event.name}]__ ${reason}`,
|
||||
});
|
||||
}
|
||||
|
||||
async moveToVoiceChannelAction(action: MoveToVoiceChannelAction, values: any, event: CustomEvent, eventData: any) {
|
||||
const targetId = await renderTemplate(action.target, values, false);
|
||||
const target = await this.getMember(targetId);
|
||||
if (!target) throw new ActionError("Unknown target member");
|
||||
|
||||
if (event.trigger.type === "command" && !this.canActOn((eventData.msg as Message).member, target)) {
|
||||
throw new ActionError("Missing permissions");
|
||||
}
|
||||
|
||||
const targetChannelId = await renderTemplate(action.channel, values, false);
|
||||
const targetChannel = this.guild.channels.get(targetChannelId);
|
||||
if (!targetChannel) throw new ActionError("Unknown target channel");
|
||||
if (!(targetChannel instanceof VoiceChannel)) throw new ActionError("Target channel is not a voice channel");
|
||||
|
||||
if (!target.voiceState.channelID) return;
|
||||
await target.edit({
|
||||
channelID: targetChannel.id,
|
||||
});
|
||||
}
|
||||
|
||||
async messageAction(action: MessageAction, values: any) {
|
||||
const targetChannelId = await renderTemplate(action.channel, values, false);
|
||||
const targetChannel = this.guild.channels.get(targetChannelId);
|
||||
if (!targetChannel) throw new ActionError("Unknown target channel");
|
||||
if (!(targetChannel instanceof TextChannel)) throw new ActionError("Target channel is not a text channel");
|
||||
|
||||
await targetChannel.createMessage({ content: action.content });
|
||||
}
|
||||
}
|
|
@ -75,8 +75,8 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
dm_on_mute: false,
|
||||
message_on_mute: false,
|
||||
message_channel: null,
|
||||
mute_message: "You have been muted on {guildName}. Reason given: {reason}",
|
||||
timed_mute_message: "You have been muted on {guildName} for {time}. Reason given: {reason}",
|
||||
mute_message: "You have been muted on the {guildName} server. Reason given: {reason}",
|
||||
timed_mute_message: "You have been muted on the {guildName} server for {time}. Reason given: {reason}",
|
||||
|
||||
can_view_list: false,
|
||||
can_cleanup: false,
|
||||
|
|
|
@ -21,6 +21,7 @@ import { WelcomeMessagePlugin } from "./WelcomeMessage";
|
|||
import { BotControlPlugin } from "./BotControl";
|
||||
import { LogServerPlugin } from "./LogServer";
|
||||
import { UsernameSaver } from "./UsernameSaver";
|
||||
import { CustomEventsPlugin } from "./CustomEvents";
|
||||
|
||||
/**
|
||||
* Plugins available to be loaded for individual guilds
|
||||
|
@ -46,6 +47,7 @@ export const availablePlugins = [
|
|||
SelfGrantableRolesPlugin,
|
||||
RemindersPlugin,
|
||||
WelcomeMessagePlugin,
|
||||
CustomEventsPlugin,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import has from "lodash.has";
|
||||
import at from "lodash.at";
|
||||
import { has, get } from "./utils";
|
||||
|
||||
const TEMPLATE_CACHE_SIZE = 200;
|
||||
const templateCache: Map<string, ParsedTemplate> = new Map();
|
||||
|
@ -219,7 +218,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
|||
}
|
||||
|
||||
async function evaluateTemplateVariable(theVar: ITemplateVar, values) {
|
||||
let value = has(values, theVar.identifier) ? at(values, theVar.identifier)[0] : undefined;
|
||||
const value = has(values, theVar.identifier) ? get(values, theVar.identifier) : undefined;
|
||||
|
||||
if (typeof value === "function") {
|
||||
const args = [];
|
||||
|
|
16
src/utils.ts
16
src/utils.ts
|
@ -5,7 +5,6 @@ import {
|
|||
Guild,
|
||||
GuildAuditLogEntry,
|
||||
Member,
|
||||
MessageContent,
|
||||
TextableChannel,
|
||||
TextChannel,
|
||||
User,
|
||||
|
@ -61,8 +60,19 @@ export function errorMessage(str) {
|
|||
return `⚠ ${str}`;
|
||||
}
|
||||
|
||||
export function uclower(str) {
|
||||
return str[0].toLowerCase() + str.slice(1);
|
||||
export function get(obj, path, def?): any {
|
||||
let cursor = obj;
|
||||
const pathParts = path.split(".");
|
||||
for (const part of pathParts) {
|
||||
cursor = cursor[part];
|
||||
if (cursor === undefined) return def;
|
||||
if (cursor == null) return null;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
export function has(obj, path): boolean {
|
||||
return get(obj, path) !== undefined;
|
||||
}
|
||||
|
||||
export function stripObjectToScalars(obj, includedNested: string[] = []) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"severity": "warning"
|
||||
},
|
||||
"no-bitwise": false,
|
||||
"interface-over-type-literal": false
|
||||
"interface-over-type-literal": false,
|
||||
"interface-name": false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue