From 411b2ff04564dc58acf08316ce7b2c672999c51f Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Fri, 20 Jul 2018 00:03:11 -0600 Subject: [PATCH] Migrated to using typeorm+postgres for database --- app/tsconfig.json | 4 +- common/ErrorCode.ts | 3 + common/TokenClaims.ts | 2 +- common/tsconfig.json | 4 +- package.json | 9 +- server/Database.ts | 85 +++++++ server/entities/SprinklersDevice.ts | 38 +++ server/entities/User.ts | 44 ++++ server/entities/index.ts | 2 + server/express/authentication.ts | 16 +- server/express/index.ts | 11 +- server/index.ts | 1 + server/models/Database.ts | 71 ------ server/models/SprinklersDevice.ts | 152 ------------ server/models/User.ts | 118 --------- server/models/index.ts | 3 - .../SprinklersDeviceRepository.ts | 10 + server/repositories/UserRepository.ts | 10 + server/repositories/index.ts | 2 + server/sprinklersRpc/websocketServer.ts | 4 +- server/state.ts | 2 +- server/tsconfig.json | 59 ++--- yarn.lock | 234 +++++++++++++++--- 23 files changed, 456 insertions(+), 428 deletions(-) create mode 100644 server/Database.ts create mode 100644 server/entities/SprinklersDevice.ts create mode 100644 server/entities/User.ts create mode 100644 server/entities/index.ts delete mode 100644 server/models/Database.ts delete mode 100644 server/models/SprinklersDevice.ts delete mode 100644 server/models/User.ts delete mode 100644 server/models/index.ts create mode 100644 server/repositories/SprinklersDeviceRepository.ts create mode 100644 server/repositories/UserRepository.ts create mode 100644 server/repositories/index.ts diff --git a/app/tsconfig.json b/app/tsconfig.json index 44af363..106d80e 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -3,9 +3,9 @@ "sourceMap": true, "jsx": "react", "experimentalDecorators": true, - "target": "es6", + "target": "es2017", "lib": [ - "es6", + "es2017", "dom", "scripthost" ], diff --git a/common/ErrorCode.ts b/common/ErrorCode.ts index 8dd5389..addfb3e 100644 --- a/common/ErrorCode.ts +++ b/common/ErrorCode.ts @@ -8,6 +8,7 @@ export enum ErrorCode { Unauthorized = 106, NoPermission = 107, NotImplemented = 108, + NotFound = 109, Internal = 200, Timeout = 300, ServerDisconnected = 301, @@ -27,6 +28,8 @@ export function toHttpStatus(errorCode: ErrorCode): number { return 401; // Unauthorized case ErrorCode.NoPermission: return 403; // Forbidden + case ErrorCode.NotFound: + return 404; case ErrorCode.NotImplemented: return 501; case ErrorCode.Internal: diff --git a/common/TokenClaims.ts b/common/TokenClaims.ts index 532c04b..b956358 100644 --- a/common/TokenClaims.ts +++ b/common/TokenClaims.ts @@ -1,7 +1,7 @@ export default interface TokenClaims { iss: string; type: "access" | "refresh"; - aud: string; + aud: number; name: string; exp: number; } diff --git a/common/tsconfig.json b/common/tsconfig.json index de25429..4448bb0 100644 --- a/common/tsconfig.json +++ b/common/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "experimentalDecorators": true, - "target": "es6", + "target": "es2017", "lib": [ - "es6" + "es2017" ], "types": [ "node" diff --git a/package.json b/package.json index 61af88f..97b46bf 100644 --- a/package.json +++ b/package.json @@ -43,14 +43,17 @@ "express-promise-router": "^3.0.2", "fork-ts-checker-webpack-plugin": "^0.4.2", "jsonwebtoken": "^8.3.0", + "lodash": "^4.17.10", "mobx": "^5.0.3", "mobx-utils": "^5.0.0", "module-alias": "^2.1.0", "moment": "^2.22.2", "mqtt": "^2.18.1", + "pg": "^7.4.3", "pino": "^4.17.3", - "rethinkdb": "^2.3.3", + "reflect-metadata": "^0.1.12", "serializr": "^1.2.0", + "typeorm": "^0.2.7", "uglify-es": "3.3.9", "ws": "^5.2.1" }, @@ -61,7 +64,7 @@ "@types/core-js": "^2.5.0", "@types/express": "^4.16.0", "@types/jsonwebtoken": "^7.2.7", - "@types/lodash-es": "^4.17.0", + "@types/lodash": "^4.14.112", "@types/node": "^10.3.5", "@types/object-assign": "^4.0.30", "@types/pino": "^4.16.0", @@ -70,7 +73,6 @@ "@types/react-dom": "16.0.6", "@types/react-hot-loader": "^4.1.0", "@types/react-router-dom": "^4.2.7", - "@types/rethinkdb": "^2.3.11", "@types/webpack-env": "^1.13.6", "@types/ws": "^5.1.2", "async": "^2.6.1", @@ -83,7 +85,6 @@ "font-awesome": "^4.7.0", "happypack": "^5.0.0", "html-webpack-plugin": "^3.2.0", - "lodash-es": "^4.17.10", "mini-css-extract-plugin": "^0.4.0", "mobx-react": "^5.2.3", "mobx-react-devtools": "^5.0.1", diff --git a/server/Database.ts b/server/Database.ts new file mode 100644 index 0000000..25074b7 --- /dev/null +++ b/server/Database.ts @@ -0,0 +1,85 @@ +import * as path from "path"; +import { Connection, createConnection, EntityManager, getConnectionOptions, Repository } from "typeorm"; + +import logger from "../common/logger"; + +import { SprinklersDevice, User } from "./entities"; +import { SprinklersDeviceRepository, UserRepository } from "./repositories/"; + +export class Database { + users!: UserRepository; + sprinklersDevices!: SprinklersDeviceRepository; + + private _conn: Connection | null = null; + + get conn(): Connection { + if (this._conn == null) { + throw new Error("Not connected to rethinkDB"); + } + return this._conn; + } + + async connect() { + const options = await getConnectionOptions(); + Object.assign(options, { + entities: [ + path.resolve(__dirname, "entities", "*.js"), + ], + }); + this._conn = await createConnection(options); + this.users = this._conn.getCustomRepository(UserRepository); + this.sprinklersDevices = this._conn.getCustomRepository(SprinklersDeviceRepository); + } + + async disconnect() { + if (this._conn) { + return this._conn.close(); + } + } + + async createAll() { + await this.conn.synchronize(); + await this.insertData(); + } + + async insertData() { + const NUM = 100; + const users: User[] = []; + for (let i = 0; i < NUM; i++) { + const username = "alex" + i; + let user = await this.users.findByUsername(username); + if (!user) { + user = await this.users.create({ + name: "Alex Mikhalev" + i, + username, + }); + } + await user.setPassword("kakashka" + i); + users.push(user); + } + + for (let i = 0; i < NUM; i++) { + const name = "test" + i; + let device = await this.sprinklersDevices.findByName(name); + if (!device) { + device = await this.sprinklersDevices.create({ + name, + }); + } + await this.sprinklersDevices.save(device); + for (let j = 0; j < 5; j++) { + const userIdx = (i + j * 10) % NUM; + const user = users[userIdx]; + user.devices = (user.devices || []).concat([device]); + } + } + logger.info("inserted/updated devices"); + + await this.users.save(users); + logger.info("inserted/updated users"); + + const alex2 = await this.users.findOne({ username: "alex0" }); + logger.info("password valid: " + await alex2!.comparePassword("kakashka0")); + + } +} diff --git a/server/entities/SprinklersDevice.ts b/server/entities/SprinklersDevice.ts new file mode 100644 index 0000000..107b697 --- /dev/null +++ b/server/entities/SprinklersDevice.ts @@ -0,0 +1,38 @@ +import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; + +import { User } from "./User"; + +@Entity() +export class SprinklersDevice { + @PrimaryGeneratedColumn() + id!: number; + + @Column() + name: string = ""; + + @ManyToMany((type) => User) + users: User[] | undefined; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} + +// @Entity() +export class UserSprinklersDevice { + @PrimaryGeneratedColumn() + id!: number; + + @Column() + userId: string = ""; + @Column() + sprinklersDeviceId: string = ""; + + constructor(data?: UserSprinklersDevice) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/server/entities/User.ts b/server/entities/User.ts new file mode 100644 index 0000000..4d9263a --- /dev/null +++ b/server/entities/User.ts @@ -0,0 +1,44 @@ +import * as bcrypt from "bcrypt"; +import { omit } from "lodash"; +import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; + +import { SprinklersDevice} from "./SprinklersDevice"; + +const HASH_ROUNDS = 1; + +@Entity() +export class User { + @PrimaryGeneratedColumn() + id!: number; + + @Column({ unique: true }) + username: string = ""; + + @Column() + name: string = ""; + + @Column() + passwordHash: string = ""; + + @ManyToMany((type) => SprinklersDevice) + @JoinTable({ name: "user_sprinklers_device" }) + devices: SprinklersDevice[] | undefined; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } + + async setPassword(newPassword: string): Promise { + this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS); + } + + async comparePassword(password: string): Promise { + return bcrypt.compare(password, this.passwordHash); + } + + toJSON() { + return omit(this, "passwordHash"); + } +} diff --git a/server/entities/index.ts b/server/entities/index.ts new file mode 100644 index 0000000..d56d7aa --- /dev/null +++ b/server/entities/index.ts @@ -0,0 +1,2 @@ +export { User } from "./User"; +export { SprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice"; diff --git a/server/express/authentication.ts b/server/express/authentication.ts index 5690e7b..0c25694 100644 --- a/server/express/authentication.ts +++ b/server/express/authentication.ts @@ -2,17 +2,17 @@ import * as Express from "express"; import Router from "express-promise-router"; import * as jwt from "jsonwebtoken"; -import TokenClaims from "@common/TokenClaims"; +import ApiError from "@common/ApiError"; +import { ErrorCode } from "@common/ErrorCode"; import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantRequest, TokenGrantResponse, } from "@common/httpApi"; -import { ErrorCode } from "@common/ErrorCode"; -import { User } from "../models/User"; +import TokenClaims from "@common/TokenClaims"; +import { User } from "../entities"; import { ServerState } from "../state"; -import ApiError from "@common/ApiError"; export { TokenClaims }; @@ -72,7 +72,7 @@ export function verifyToken(token: string): Promise { function generateAccessToken(user: User, secret: string): Promise { const access_token_claims: TokenClaims = { iss: "sprinklers3", - aud: user.id || "", + aud: user.id, name: user.name, type: "access", exp: getExpTime(ACCESS_TOKEN_LIFETIME), @@ -84,7 +84,7 @@ function generateAccessToken(user: User, secret: string): Promise { function generateRefreshToken(user: User, secret: string): Promise { const refresh_token_claims: TokenClaims = { iss: "sprinklers3", - aud: user.id || "", + aud: user.id, name: user.name, type: "refresh", exp: getExpTime(REFRESH_TOKEN_LIFETIME), @@ -102,7 +102,7 @@ export function authentication(state: ServerState) { if (!body || !username || !password) { throw new ApiError("Must specify username and password"); } - const user = await User.loadByUsername(state.database, username); + const user = await state.database.users.findByUsername(username); if (!user) { throw new ApiError("User does not exist"); } @@ -123,7 +123,7 @@ export function authentication(state: ServerState) { if (claims.type !== "refresh") { throw new ApiError("Not a refresh token"); } - const user = await User.load(state.database, claims.aud); + const user = await state.database.users.findOne(claims.aud); if (!user) { throw new ApiError("User no longer exists"); } diff --git a/server/express/index.ts b/server/express/index.ts index ced249a..726811c 100644 --- a/server/express/index.ts +++ b/server/express/index.ts @@ -7,7 +7,8 @@ import { ServerState } from "../state"; import requestLogger from "./requestLogger"; import serveApp from "./serveApp"; -import { User } from "../models/User"; +import ApiError from "@common/ApiError"; +import { ErrorCode } from "@common/ErrorCode"; import { authentication } from "./authentication"; import errorHandler from "./errorHandler"; @@ -25,7 +26,7 @@ export function createApp(state: ServerState) { }); app.get("/api/users", (req, res) => { - User.loadAll(state.database) + state.database.users.find() .then((users) => { res.json({ data: users, @@ -34,8 +35,12 @@ export function createApp(state: ServerState) { }); app.get("/api/users/:username", (req, res, next) => { - User.loadByUsername(state.database, req.params.username) + const { username } = req.params; + state.database.users.findByUsername(username) .then((user) => { + if (!user) { + throw new ApiError(`user ${username} does not exist`, ErrorCode.NotFound); + } res.json({ data: user, }); diff --git a/server/index.ts b/server/index.ts index 9ecc811..4318003 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,4 +1,5 @@ /* tslint:disable:ordered-imports */ +import "reflect-metadata"; import "./configureAlias"; import "env"; import "./configureLogger"; diff --git a/server/models/Database.ts b/server/models/Database.ts deleted file mode 100644 index d50e30e..0000000 --- a/server/models/Database.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as r from "rethinkdb"; - -import logger from "@common/logger"; -import { SprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice"; -import { User } from "./User"; - -export class Database { - static readonly databaseName = "sprinklers3"; - - db: r.Db; - private _conn: r.Connection | null = null; - - get conn(): r.Connection { - if (this._conn == null) { - throw new Error("Not connected to rethinkDB"); - } - return this._conn; - } - - constructor() { - this.db = r.db(Database.databaseName); - } - - async connect() { - this._conn = await r.connect("localhost"); - } - - async disconnect() { - if (this._conn) { - return this._conn.close(); - } - } - - async createAll() { - const dbs = await r.dbList().run(this.conn); - if (dbs.indexOf(Database.databaseName) === -1) { - await r.dbCreate(Database.databaseName).run(this.conn); - } - await this.createTables(); - } - - async createTables() { - const tables = await this.db.tableList().run(this.conn); - if (tables.indexOf(User.tableName) === -1) { - await User.createTable(this); - } - if (tables.indexOf(SprinklersDevice.tableName) === -1) { - await SprinklersDevice.createTable(this); - } - if (tables.indexOf(UserSprinklersDevice.tableName) === -1) { - await UserSprinklersDevice.createTable(this); - } - const alex = new User(this, { - name: "Alex Mikhalev", - username: "alex", - }); - await alex.setPassword("kakashka"); - const created = await alex.createOrUpdate(); - logger.info((created ? "created" : "updated") + " user alex"); - - const alex2 = await User.loadByUsername(this, "alex"); - logger.info("password valid: " + await alex2!.comparePassword("kakashka")); - - const device = new SprinklersDevice(this, { - name: "test", - }); - await device.createOrUpdate(); - - device.addToUser - } -} diff --git a/server/models/SprinklersDevice.ts b/server/models/SprinklersDevice.ts deleted file mode 100644 index 2b7f131..0000000 --- a/server/models/SprinklersDevice.ts +++ /dev/null @@ -1,152 +0,0 @@ -import * as r from "rethinkdb"; -import { createModelSchema, primitive, serialize, update } from "serializr"; - -import { Database } from "./Database"; -import { User } from "./User"; - -export interface ISprinklersDevice { - id: string | undefined; - name: string; -} - -export class SprinklersDevice implements ISprinklersDevice { - static readonly tableName = "SprinklersDevices"; - - id: string | undefined; - name: string = ""; - - private db: Database; - private _table: r.Table | null = null; - - constructor(db: Database, data?: Partial) { - this.db = db; - if (data) { - update(this, data); - } - } - - static async createTable(db: Database) { - await db.db.tableCreate(SprinklersDevice.tableName).run(db.conn); - await db.db.table(SprinklersDevice.tableName).indexCreate("name").run(db.conn); - } - - static async loadAll(db: Database): Promise { - const cursor = await db.db.table(SprinklersDevice.tableName) - .run(db.conn); - const users = await cursor.toArray(); - return users.map((data) => { - return new SprinklersDevice(db, data); - }); - } - - static async load(db: Database, id: string): Promise { - const data = await db.db.table(SprinklersDevice.tableName) - .get(id) - .run(db.conn); - if (data == null) { - return null; - } - return new SprinklersDevice(db, data); - } - - private static getTable(db: Database): r.Table { - return db.db.table(this.tableName); - } - - private get table() { - if (!this._table) { - this._table = SprinklersDevice.getTable(this.db); - } - return this._table; - } - - async create() { - const data = serialize(this); - delete data.id; - const result = await this.table - .insert(data) - .run(this.db.conn); - this.id = result.generated_keys[0]; - } - - async createOrUpdate() { - const data = serialize(this); - delete data.id; - const device = this.table.filter(r.row("name").eq(this.name)); - const nameDoesNotExist = device.isEmpty(); - const a: r.WriteResult = await r.branch(nameDoesNotExist, - this.table.insert(data) as r.Expression, - device.nth(0).update(data) as r.Expression) - .run(this.db.conn); - if (a.inserted > 0) { - this.id = a.generated_keys[0]; - return true; - } else { - return false; - } - } - - async addToUser(user: User | number) { - const userId = (typeof user === "number") ? user : user.id; - const userDevice = new UserSprinklersDevice(this.db, { - - }); - } - - toJSON(): any { - return serialize(this); - } -} - -createModelSchema(SprinklersDevice, { - id: primitive(), - name: primitive(), -}); - -export interface IUserSprinklersDevice { - id: string | undefined; - userId: string; - sprinklersDeviceId: string; -} - -export class UserSprinklersDevice implements IUserSprinklersDevice { - static readonly tableName = "UserSprinklersDevices"; - - id: string | undefined; - - userId: string = ""; - sprinklersDeviceId: string = ""; - - private db: Database; - private _table: r.Table | null = null; - - constructor(db: Database) { - this.db = db; - } - - static async createTable(db: Database) { - await db.db.tableCreate(UserSprinklersDevice.tableName).run(db.conn); - await db.db.table(UserSprinklersDevice.tableName).indexCreate("userId").run(db.conn); - await db.db.table(UserSprinklersDevice.tableName).indexCreate("sprinklersDeviceId").run(db.conn); - } - - private static getTable(db: Database): r.Table { - return db.db.table(this.tableName); - } - - async create() { - const data = serialize(this); - delete data.id; - const result = await this.table - .insert(data) - .run(this.db.conn); - this.id = result.generated_keys[0]; - } - - private get table() { - if (!this._table) { - this._table = UserSprinklersDevice.getTable(this.db); - } - return this._table; - } -} diff --git a/server/models/User.ts b/server/models/User.ts deleted file mode 100644 index 41c48ea..0000000 --- a/server/models/User.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as bcrypt from "bcrypt"; -import * as r from "rethinkdb"; -import { createModelSchema, primitive, serialize, update } from "serializr"; - -import { Database } from "./Database"; - -export interface IUser { - id: string | undefined; - username: string; - name: string; - passwordHash: string; -} - -const HASH_ROUNDS = 10; - -export class User implements IUser { - static readonly tableName = "Users"; - - id: string | undefined; - username: string = ""; - name: string = ""; - passwordHash: string = ""; - - private db: Database; - - private get table() { - return this.db.db.table(User.tableName); - } - - constructor(db: Database, data?: Partial) { - this.db = db; - if (data) { - update(this, data); - } - } - - static async createTable(db: Database) { - await db.db.tableCreate(User.tableName).run(db.conn); - await db.db.table(User.tableName).indexCreate("username").run(db.conn); - } - - static async loadAll(db: Database): Promise { - const cursor = await db.db.table(User.tableName) - .run(db.conn); - const users = await cursor.toArray(); - return users.map((data) => { - return new User(db, data); - }); - } - - static async load(db: Database, id: string): Promise { - const data = await db.db.table(User.tableName) - .get(id) - .run(db.conn); - if (data == null) { - return null; - } - return new User(db, data); - } - - static async loadByUsername(db: Database, username: string): Promise { - const seq = await db.db.table(User.tableName) - .filter(r.row("username").eq(username)) - .run(db.conn); - const data = await seq.toArray(); - if (data.length === 0) { - return null; - } - return new User(db, data[0]); - } - - async create() { - const data = serialize(this); - delete data.id; - const result = await this.table - .insert(data) - .run(this.db.conn); - this.id = result.generated_keys[0]; - } - - async createOrUpdate() { - const data = serialize(this); - delete data.id; - const user = this.table.filter(r.row("username").eq(this.username)); - const usernameDoesNotExist = user.isEmpty(); - const a: r.WriteResult = await r.branch(usernameDoesNotExist, - this.table.insert(data) as r.Expression, - user.nth(0).update(data) as r.Expression) - .run(this.db.conn); - if (a.inserted > 0) { - this.id = a.generated_keys[0]; - return true; - } else { - return false; - } - } - - async setPassword(newPassword: string): Promise { - this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS); - } - - async comparePassword(password: string): Promise { - return bcrypt.compare(password, this.passwordHash); - } - - toJSON(): any { - const data = serialize(this); - delete data.passwordHash; - return data; - } -} - -createModelSchema(User, { - id: primitive(), - username: primitive(), - name: primitive(), - passwordHash: primitive(), -}); diff --git a/server/models/index.ts b/server/models/index.ts deleted file mode 100644 index c3de530..0000000 --- a/server/models/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { User, IUser } from "./User"; -export { SprinklersDevice, ISprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice"; -export { Database } from "./Database"; diff --git a/server/repositories/SprinklersDeviceRepository.ts b/server/repositories/SprinklersDeviceRepository.ts new file mode 100644 index 0000000..ac1e0da --- /dev/null +++ b/server/repositories/SprinklersDeviceRepository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from "typeorm"; + +import { SprinklersDevice } from "../entities"; + +@EntityRepository(SprinklersDevice) +export class SprinklersDeviceRepository extends Repository { + findByName(name: string) { + return this.findOne({ name }); + } +} diff --git a/server/repositories/UserRepository.ts b/server/repositories/UserRepository.ts new file mode 100644 index 0000000..0f625a5 --- /dev/null +++ b/server/repositories/UserRepository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from "typeorm"; + +import { User } from "../entities"; + +@EntityRepository(User) +export class UserRepository extends Repository { + findByUsername(username: string) { + return this.findOne({ username }, { relations: ["devices"] }); + } +} diff --git a/server/repositories/index.ts b/server/repositories/index.ts new file mode 100644 index 0000000..64680c6 --- /dev/null +++ b/server/repositories/index.ts @@ -0,0 +1,2 @@ +export { UserRepository } from "./UserRepository"; +export { SprinklersDeviceRepository } from "./SprinklersDeviceRepository"; diff --git a/server/sprinklersRpc/websocketServer.ts b/server/sprinklersRpc/websocketServer.ts index 49b6a40..70068fa 100644 --- a/server/sprinklersRpc/websocketServer.ts +++ b/server/sprinklersRpc/websocketServer.ts @@ -2,10 +2,10 @@ import { autorun } from "mobx"; import { serialize } from "serializr"; import * as WebSocket from "ws"; +import { ErrorCode } from "@common/ErrorCode"; import * as rpc from "@common/jsonRpc"; import log from "@common/logger"; import * as deviceRequests from "@common/sprinklersRpc/deviceRequests"; -import { ErrorCode } from "@common/ErrorCode"; import * as schema from "@common/sprinklersRpc/schema"; import * as ws from "@common/sprinklersRpc/websocketData"; import { TokenClaims, verifyToken } from "../express/authentication"; @@ -21,7 +21,7 @@ export class WebSocketClient { deviceSubscriptions: string[] = []; /// This shall be the user id if the client has been authenticated, null otherwise - userId: string | null = null; + userId: number | null = null; get state() { return this.api.state; diff --git a/server/state.ts b/server/state.ts index c86e8a0..0604644 100644 --- a/server/state.ts +++ b/server/state.ts @@ -1,6 +1,6 @@ import logger from "@common/logger"; import * as mqtt from "@common/sprinklersRpc/mqtt"; -import { Database } from "./models/Database"; +import { Database } from "./Database"; export class ServerState { mqttClient: mqtt.MqttRpcClient; diff --git a/server/tsconfig.json b/server/tsconfig.json index 90d00de..5050664 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,31 +1,32 @@ { - "compilerOptions": { - "outDir": "../dist", - "sourceMap": true, - "experimentalDecorators": true, - "target": "es2017", - "module": "commonjs", - "lib": [ - "es6", - "dom" - ], - "types": [ - "webpack-env", - "node" - ], - "strict": true, - "allowJs": true, - "baseUrl": "..", - "paths": { - "@common/*": [ - "./common/*" - ], - "@app/*": [ - "./app/*" - ] - } - }, - "include": [ - "./**/*.ts" - ] + "compilerOptions": { + "outDir": "../dist", + "sourceMap": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "target": "es2017", + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "types": [ + "webpack-env", + "node" + ], + "strict": true, + "allowJs": true, + "baseUrl": "..", + "paths": { + "@common/*": [ + "./common/*" + ], + "@app/*": [ + "./app/*" + ] + } + }, + "include": [ + "./**/*.ts" + ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7fc62cb..4ef5eb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,15 +82,9 @@ dependencies: "@types/node" "*" -"@types/lodash-es@^4.17.0": - version "4.17.0" - resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.0.tgz#ed9044d62ee36a93e0650b112701986b1c74c766" - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.110" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.110.tgz#fb07498f84152947f30ea09d89207ca07123461e" +"@types/lodash@^4.14.112": + version "4.14.112" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.112.tgz#4a8d8e5716b97a1ac01fe1931ad1e4cba719de5a" "@types/mime@*": version "2.0.0" @@ -154,12 +148,6 @@ dependencies: csstype "^2.2.0" -"@types/rethinkdb@^2.3.11": - version "2.3.11" - resolved "https://registry.yarnpkg.com/@types/rethinkdb/-/rethinkdb-2.3.11.tgz#13b7148c0fd1f4e65ea1e5a242fbae01ccc3259b" - dependencies: - "@types/node" "*" - "@types/serve-static@*": version "1.13.2" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" @@ -411,6 +399,10 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -425,6 +417,10 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +app-root-path@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -715,10 +711,6 @@ block-stream@*: dependencies: inherits "~2.0.0" -"bluebird@>= 2.3.2 < 3": - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -902,6 +894,10 @@ buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" +buffer-writer@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -914,6 +910,13 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.1.0.tgz#c913e43678c7cb7c8bd16afbcddb6c5505e8f9fe" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1049,7 +1052,7 @@ chalk@1.1.3, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.0, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1159,6 +1162,16 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-highlight@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-1.2.3.tgz#b200f97ed0e43d24633e89de0f489a48bb87d2bf" + dependencies: + chalk "^2.3.0" + highlight.js "^9.6.0" + mz "^2.4.0" + parse5 "^3.0.3" + yargs "^10.0.3" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -1836,6 +1849,10 @@ dot-prop@^4.1.0: dependencies: is-obj "^1.0.0" +dotenv@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + dotenv@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.0.0.tgz#24e37c041741c5f4b25324958ebbc34bca965935" @@ -2288,6 +2305,10 @@ fbjs@^0.8.16: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +figlet@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2847,6 +2868,10 @@ help-me@^1.0.1: through2 "^2.0.1" xtend "^4.0.0" +highlight.js@^9.6.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -3527,7 +3552,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.4.3, js-yaml@^3.7.0: +js-yaml@^3.11.0, js-yaml@^3.4.3, js-yaml@^3.7.0: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" dependencies: @@ -3724,10 +3749,6 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.17.10: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" - lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -4227,6 +4248,14 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@2.10.0, nan@^2.10.0, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -4690,6 +4719,10 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" +packet-reader@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" + pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -4708,6 +4741,10 @@ param-case@2.1.x: dependencies: no-case "^2.2.0" +parent-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" + parse-asn1@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" @@ -4848,6 +4885,41 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" +pg-connection-string@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" + +pg-pool@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.3.tgz#c022032c8949f312a4f91fb6409ce04076be3257" + +pg-types@~1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.1.tgz#d64087e3903b58ffaad279e7595c52208a14c3d2" + dependencies: + postgres-array "~1.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.0" + postgres-interval "^1.1.0" + +pg@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-7.4.3.tgz#f7b6f93f5340ecc2596afbb94a13e3d6b609834b" + dependencies: + buffer-writer "1.0.1" + packet-reader "0.3.1" + pg-connection-string "0.1.3" + pg-pool "~2.0.3" + pg-types "~1.12.1" + pgpass "1.x" + semver "4.3.2" + +pgpass@1.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" + dependencies: + split "^1.0.0" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5436,6 +5508,24 @@ postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.16, postcss@^6.0.18, source-map "^0.6.1" supports-color "^5.4.0" +postgres-array@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.2.tgz#8e0b32eb03bf77a5c0a7851e0441c169a256a238" + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + +postgres-date@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" + +postgres-interval@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.2.tgz#bf71ff902635f21cb241a013fc421d81d1db15a9" + dependencies: + xtend "^4.0.0" + prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -5817,6 +5907,10 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" +reflect-metadata@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" + regenerate@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" @@ -6015,12 +6109,6 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -rethinkdb@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/rethinkdb/-/rethinkdb-2.3.3.tgz#3dc6586e22fa1dabee0d254e64bd0e379fad2f72" - dependencies: - bluebird ">= 2.3.2 < 3" - right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -6105,7 +6193,7 @@ sass-loader@^7.0.3: neo-async "^2.5.0" pify "^3.0.0" -sax@^1.2.4, sax@~1.2.1: +sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -6160,6 +6248,10 @@ semver-diff@^2.0.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +semver@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -6503,6 +6595,12 @@ split@0.3: dependencies: through "2" +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6733,6 +6831,18 @@ text-table@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + dependencies: + any-promise "^1.0.0" + through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -6910,6 +7020,24 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typeorm@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.7.tgz#4bbbace80dc91b1303be13f42d44ebf01d1b2558" + dependencies: + app-root-path "^2.0.1" + buffer "^5.1.0" + chalk "^2.3.2" + cli-highlight "^1.2.3" + debug "^3.1.0" + dotenv "^5.0.1" + glob "^7.1.2" + js-yaml "^3.11.0" + mkdirp "^0.5.1" + reflect-metadata "^0.1.12" + xml2js "^0.4.17" + yargonaut "^1.1.2" + yargs "^11.1.0" + typescript@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" @@ -7464,6 +7592,17 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" +xml2js@^0.4.17: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" @@ -7488,12 +7627,26 @@ yallist@^3.0.0, yallist@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" +yargonaut@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.3.tgz#1cf6bbe511ecc865d6014203385628bcc39b0493" + dependencies: + chalk "^1.1.1" + figlet "^1.1.1" + parent-require "^1.0.0" + yargs-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" dependencies: camelcase "^3.0.0" +yargs-parser@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" + dependencies: + camelcase "^4.1.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -7517,6 +7670,23 @@ yargs@11.0.0, yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" +yargs@^10.0.3: + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^8.1.0" + yargs@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"