Initial dashboard work (auth flow)
This commit is contained in:
parent
d54897acdd
commit
5a91d36953
18 changed files with 3808 additions and 31 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -71,7 +71,9 @@ desktop.ini
|
||||||
|
|
||||||
# Compiled files
|
# Compiled files
|
||||||
/dist
|
/dist
|
||||||
|
/dist-frontend
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
/convert.js
|
/convert.js
|
||||||
/startscript.js
|
/startscript.js
|
||||||
|
/.cache
|
||||||
|
|
5
nodemon-dashboard-api.json
Normal file
5
nodemon-dashboard-api.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"watch": "src/dashboard/api",
|
||||||
|
"ext": "ts",
|
||||||
|
"exec": "ts-node ./src/dashboard/api/index.ts"
|
||||||
|
}
|
3397
package-lock.json
generated
3397
package-lock.json
generated
File diff suppressed because it is too large
Load diff
32
package.json
32
package.json
|
@ -5,39 +5,43 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest src",
|
"test": "jest src",
|
||||||
"start-dev": "ts-node src/index.ts",
|
"start-bot-dev": "ts-node src/index.ts",
|
||||||
"start-watch": "nodemon",
|
"start-bot-watch": "nodemon --config nodemon-bot.json",
|
||||||
|
"start-bot-prod": "cross-env NODE_ENV=production node dist/index.js",
|
||||||
"format": "prettier --write \"./**/*.ts\"",
|
"format": "prettier --write \"./**/*.ts\"",
|
||||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
|
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
|
||||||
"build": "rimraf dist && tsc",
|
"build": "rimraf dist && tsc",
|
||||||
"start-prod": "cross-env NODE_ENV=production node dist/index.js",
|
"build-dashboard-frontend": "rimraf dist-frontend && parcel build src/dashboard/frontend/index.html --out-dir dist-frontend",
|
||||||
"migrate": "npm run typeorm -- migration:run"
|
"start-dashboard-frontend-dev": "parcel src/dashboard/frontend/index.html",
|
||||||
|
"start-dashboard-api-dev": "ts-node src/dashboard/server/index.ts",
|
||||||
|
"start-dashboard-api-watch": "nodemon --config nodemon-dashboard-api.json",
|
||||||
|
"start-dashboard-api": "cross-env NODE_ENV=production node dist/dashboard/server/index.js",
|
||||||
|
"migrate": "npm run typeorm -- migration:run",
|
||||||
|
"migrate-rollback": "npm run typeorm -- migration:revert"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lodash.at": "^4.6.3",
|
|
||||||
"@types/moment-timezone": "^0.5.6",
|
|
||||||
"ajv": "^6.7.0",
|
"ajv": "^6.7.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"dotenv": "^4.0.0",
|
"dotenv": "^4.0.0",
|
||||||
"emoji-regex": "^7.0.1",
|
"emoji-regex": "^7.0.1",
|
||||||
"eris": "github:abalabahaha/eris#dev",
|
"eris": "github:abalabahaha/eris#dev",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"express": "^4.17.0",
|
||||||
"humanize-duration": "^3.15.0",
|
"humanize-duration": "^3.15.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"knub": "^20.1.0",
|
"knub": "^20.1.0",
|
||||||
"last-commit-log": "^2.1.0",
|
"last-commit-log": "^2.1.0",
|
||||||
"lodash.at": "^4.6.0",
|
|
||||||
"lodash.chunk": "^4.2.0",
|
"lodash.chunk": "^4.2.0",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.difference": "^4.5.0",
|
"lodash.difference": "^4.5.0",
|
||||||
"lodash.has": "^4.5.2",
|
|
||||||
"lodash.intersection": "^4.4.0",
|
"lodash.intersection": "^4.4.0",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"lodash.pick": "^4.4.0",
|
||||||
"moment-timezone": "^0.5.21",
|
"moment-timezone": "^0.5.21",
|
||||||
"mysql": "^2.16.0",
|
"mysql": "^2.16.0",
|
||||||
|
"passport": "^0.4.0",
|
||||||
|
"passport-custom": "^1.0.5",
|
||||||
|
"passport-oauth2": "^1.5.0",
|
||||||
"reflect-metadata": "^0.1.12",
|
"reflect-metadata": "^0.1.12",
|
||||||
"tlds": "^1.203.1",
|
"tlds": "^1.203.1",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
|
@ -50,14 +54,20 @@
|
||||||
"@babel/core": "^7.3.4",
|
"@babel/core": "^7.3.4",
|
||||||
"@babel/preset-env": "^7.3.4",
|
"@babel/preset-env": "^7.3.4",
|
||||||
"@babel/preset-typescript": "^7.3.3",
|
"@babel/preset-typescript": "^7.3.3",
|
||||||
|
"@types/express": "^4.16.1",
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.11",
|
||||||
|
"@types/lodash.at": "^4.6.3",
|
||||||
|
"@types/moment-timezone": "^0.5.6",
|
||||||
"@types/node": "^10.12.0",
|
"@types/node": "^10.12.0",
|
||||||
|
"@types/passport": "^1.0.0",
|
||||||
|
"@types/passport-oauth2": "^1.4.8",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
"babel-jest": "^24.5.0",
|
"babel-jest": "^24.5.0",
|
||||||
"husky": "^1.3.1",
|
"husky": "^1.3.1",
|
||||||
"jest": "^24.7.1",
|
"jest": "^24.7.1",
|
||||||
"lint-staged": "^8.1.5",
|
"lint-staged": "^8.1.5",
|
||||||
"nodemon": "^1.17.5",
|
"nodemon": "^1.17.5",
|
||||||
|
"parcel-bundler": "^1.12.3",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"tslint": "^5.13.1",
|
"tslint": "^5.13.1",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{
|
{
|
||||||
"name": "zeppelin",
|
"name": "zeppelin",
|
||||||
"script": "npm",
|
"script": "npm",
|
||||||
"args": "run start-prod",
|
"args": "run start-bot-prod",
|
||||||
"log_date_format": "YYYY-MM-DD HH:mm:ss"
|
"log_date_format": "YYYY-MM-DD HH:mm:ss"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
104
src/dashboard/api/auth.ts
Normal file
104
src/dashboard/api/auth.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import express 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 simpleAPIRequest(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 default 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 simpleAPIRequest(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
|
||||||
|
cb(null, { apiKey });
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get("/auth/login", passport.authenticate("oauth2"));
|
||||||
|
app.get(
|
||||||
|
"/auth/oauth-callback",
|
||||||
|
passport.authenticate("oauth2", { failureRedirect: "/", session: false }),
|
||||||
|
(req, res) => {
|
||||||
|
res.redirect(`${process.env.DASHBOARD_URL}/login-callback/?apiKey=${req.user.apiKey}`);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
19
src/dashboard/api/index.ts
Normal file
19
src/dashboard/api/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
import express from "express";
|
||||||
|
import initAuth from "./auth";
|
||||||
|
import { connect } from "../../data/db";
|
||||||
|
|
||||||
|
console.log("Connecting to database...");
|
||||||
|
connect().then(() => {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
initAuth(app);
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.end("Hi");
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
app.listen(port, () => console.log(`API server listening on port ${port}`));
|
||||||
|
});
|
14
src/dashboard/frontend/index.html
Normal file
14
src/dashboard/frontend/index.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!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>
|
||||||
|
<button id="login-button">Login</button>
|
||||||
|
<script src="./index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
src/dashboard/frontend/index.js
Normal file
4
src/dashboard/frontend/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const API_URL = process.env.API_URL;
|
||||||
|
document.getElementById('login-button').addEventListener('click', () => {
|
||||||
|
window.location.href = `${API_URL}/auth/login`;
|
||||||
|
});
|
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 })
|
||||||
|
.where("expires_at > NOW()")
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
if (!login) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = crypto.createHash("sha256");
|
||||||
|
hash.update(token);
|
||||||
|
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(token);
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
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);
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import has from "lodash.has";
|
import { has, get } from "./utils";
|
||||||
import at from "lodash.at";
|
|
||||||
|
|
||||||
const TEMPLATE_CACHE_SIZE = 200;
|
const TEMPLATE_CACHE_SIZE = 200;
|
||||||
const templateCache: Map<string, ParsedTemplate> = new Map();
|
const templateCache: Map<string, ParsedTemplate> = new Map();
|
||||||
|
@ -219,7 +218,7 @@ export function parseTemplate(str: string): ParsedTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function evaluateTemplateVariable(theVar: ITemplateVar, values) {
|
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)[0] : undefined;
|
||||||
|
|
||||||
if (typeof value === "function") {
|
if (typeof value === "function") {
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
16
src/utils.ts
16
src/utils.ts
|
@ -5,7 +5,6 @@ import {
|
||||||
Guild,
|
Guild,
|
||||||
GuildAuditLogEntry,
|
GuildAuditLogEntry,
|
||||||
Member,
|
Member,
|
||||||
MessageContent,
|
|
||||||
TextableChannel,
|
TextableChannel,
|
||||||
TextChannel,
|
TextChannel,
|
||||||
User,
|
User,
|
||||||
|
@ -61,8 +60,19 @@ export function errorMessage(str) {
|
||||||
return `⚠ ${str}`;
|
return `⚠ ${str}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uclower(str) {
|
export function get(obj, path, def?): any {
|
||||||
return str[0].toLowerCase() + str.slice(1);
|
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[] = []) {
|
export function stripObjectToScalars(obj, includedNested: string[] = []) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"severity": "warning"
|
"severity": "warning"
|
||||||
},
|
},
|
||||||
"no-bitwise": false,
|
"no-bitwise": false,
|
||||||
"interface-over-type-literal": false
|
"interface-over-type-literal": false,
|
||||||
|
"interface-name": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue