Browse Source

Started adding some device stuff for database

update-deps
Alex Mikhalev 7 years ago
parent
commit
9aadf8e023
  1. 26
      app/components/DeviceView.tsx
  2. 2
      app/sprinklersRpc/WebSocketRpcClient.ts
  3. 15
      app/state/AppState.ts
  4. 14
      common/sprinklersRpc/ConnectionState.ts
  5. 2
      common/sprinklersRpc/SprinklersDevice.ts
  6. 13
      common/sprinklersRpc/mqtt/index.ts
  7. 14
      server/models/Database.ts
  8. 152
      server/models/SprinklersDevice.ts
  9. 12
      server/models/User.ts
  10. 3
      server/models/index.ts
  11. 4
      server/state.ts

26
app/components/DeviceView.tsx

@ -10,31 +10,27 @@ import "./DeviceView.scss";
const ConnectionState = observer(({ connectionState, className }: const ConnectionState = observer(({ connectionState, className }:
{ connectionState: ConState, className?: string }) => { { connectionState: ConState, className?: string }) => {
const connected = connectionState.isConnected; const connected = connectionState.isDeviceConnected;
const classes = classNames({
connectionState: true,
connected: connected === true,
disconnected: connected === false,
unknown: connected === null,
}, className);
let connectionText: string; let connectionText: string;
let iconName: SemanticICONS = "unlinkify"; let iconName: SemanticICONS = "unlinkify";
let clazzName: string = "disconnected";
if (connected) { if (connected) {
connectionText = "Connected"; connectionText = "Connected";
iconName = "linkify"; iconName = "linkify";
} else if (connected === null) { clazzName = "connected";
connectionText = "Unknown"; } else if (connected === false) {
iconName = "question"; connectionText = "Device Disconnected";
} else if (connectionState.noPermission) { } else if (connectionState.noPermission) {
connectionText = "No permission for this device"; connectionText = "No permission for this device";
iconName = "ban"; iconName = "ban";
} else if (connectionState.serverToBroker) { } else if (connectionState.clientToServer === false) {
connectionText = "Device Disconnected"; connectionText = "Disconnected from server";
} else if (connectionState.clientToServer) {
connectionText = "Broker Disconnected";
} else { } else {
connectionText = "Server Disconnected"; connectionText = "Unknown";
iconName = "question";
clazzName = "unknown";
} }
const classes = classNames("connectionState", clazzName, className);
return ( return (
<div className={classes}> <div className={classes}>
<Icon name={iconName}/>&nbsp; <Icon name={iconName}/>&nbsp;

2
app/sprinklersRpc/websocketClient.ts → app/sprinklersRpc/WebSocketRpcClient.ts

@ -91,7 +91,7 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
private reconnectTimer: number | null = null; private reconnectTimer: number | null = null;
get connected(): boolean { get connected(): boolean {
return this.connectionState.isConnected || false; return this.connectionState.isServerConnected || false;
} }
constructor(tokenStore: TokenStore, webSocketUrl: string = DEFAULT_URL) { constructor(tokenStore: TokenStore, webSocketUrl: string = DEFAULT_URL) {

15
app/state/AppState.ts

@ -2,9 +2,11 @@ import { createBrowserHistory, History } from "history";
import { computed } from "mobx"; import { computed } from "mobx";
import { RouterStore, syncHistoryWithStore } from "mobx-react-router"; import { RouterStore, syncHistoryWithStore } from "mobx-react-router";
import { WebSocketRpcClient } from "@app/sprinklersRpc/websocketClient"; import { WebSocketRpcClient } from "@app/sprinklersRpc/WebSocketRpcClient";
import HttpApi from "@app/state/HttpApi"; import HttpApi from "@app/state/HttpApi";
import { UiStore } from "@app/state/UiStore"; import { UiStore } from "@app/state/UiStore";
import ApiError from "@common/ApiError";
import { ErrorCode } from "@common/ErrorCode";
import log from "@common/logger"; import log from "@common/logger";
export default class AppState { export default class AppState {
@ -29,9 +31,14 @@ export default class AppState {
try { try {
await this.httpApi.tokenStore.grantRefresh(); await this.httpApi.tokenStore.grantRefresh();
} catch (err) { } catch (err) {
log.warn({ err }, "could not refresh access token. erasing token"); if (err instanceof ApiError && err.code === ErrorCode.BadToken) {
this.tokenStore.clear(); log.warn({ err }, "refresh is bad for some reason, erasing");
this.history.push("/login"); this.tokenStore.clear();
this.history.push("/login");
} else {
log.error({ err }, "could not refresh access token");
// TODO: some kind of error page?
}
} }
} else { } else {
this.history.push("/login"); this.history.push("/login");

14
common/sprinklersRpc/ConnectionState.ts

@ -45,18 +45,28 @@ export class ConnectionState {
return false; return false;
} }
@computed get isConnected(): boolean | null { @computed get isDeviceConnected(): boolean | null {
if (this.hasPermission === false) { if (this.hasPermission === false) {
return false; return false;
} }
if (this.serverToBroker === false || this.clientToServer === false) {
return null;
}
if (this.brokerToDevice != null) { if (this.brokerToDevice != null) {
return this.brokerToDevice; return this.brokerToDevice;
} }
return null;
}
@computed get isServerConnected(): boolean | null {
if (this.hasPermission === false) {
return false;
}
if (this.serverToBroker != null) { if (this.serverToBroker != null) {
return this.serverToBroker; return this.serverToBroker;
} }
if (this.clientToServer != null) { if (this.clientToServer != null) {
return this.clientToServer; return this.brokerToDevice;
} }
return null; return null;
} }

2
common/sprinklersRpc/SprinklersDevice.ts

@ -12,7 +12,7 @@ export abstract class SprinklersDevice {
@observable sectionRunner: SectionRunner; @observable sectionRunner: SectionRunner;
@computed get connected(): boolean { @computed get connected(): boolean {
return this.connectionState.isConnected || false; return this.connectionState.isDeviceConnected || false;
} }
sectionConstructor: typeof Section = Section; sectionConstructor: typeof Section = Section;

13
common/sprinklersRpc/mqtt/index.ts

@ -7,6 +7,7 @@ import * as s from "@common/sprinklersRpc";
import * as requests from "@common/sprinklersRpc/deviceRequests"; import * as requests from "@common/sprinklersRpc/deviceRequests";
import * as schema from "@common/sprinklersRpc/schema"; import * as schema from "@common/sprinklersRpc/schema";
import { seralizeRequest } from "@common/sprinklersRpc/schema/requests"; import { seralizeRequest } from "@common/sprinklersRpc/schema/requests";
import { getRandomId } from "@common/utils";
const log = logger.child({ source: "mqtt" }); const log = logger.child({ source: "mqtt" });
@ -14,14 +15,14 @@ interface WithRid {
rid: number; rid: number;
} }
export class MqttApiClient implements s.SprinklersRPC { export class MqttRpcClient implements s.SprinklersRPC {
readonly mqttUri: string; readonly mqttUri: string;
client!: mqtt.Client; client!: mqtt.Client;
@observable connectionState: s.ConnectionState = new s.ConnectionState(); @observable connectionState: s.ConnectionState = new s.ConnectionState();
devices: Map<string, MqttSprinklersDevice> = new Map(); devices: Map<string, MqttSprinklersDevice> = new Map();
get connected(): boolean { get connected(): boolean {
return this.connectionState.isConnected || false; return this.connectionState.isServerConnected || false;
} }
constructor(mqttUri: string) { constructor(mqttUri: string) {
@ -30,11 +31,11 @@ export class MqttApiClient implements s.SprinklersRPC {
} }
private static newClientId() { private static newClientId() {
return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000); return "sprinklers3-MqttApiClient-" + getRandomId();
} }
start() { start() {
const clientId = MqttApiClient.newClientId(); const clientId = MqttRpcClient.newClientId();
log.info({ mqttUri: this.mqttUri, clientId }, "connecting to mqtt broker with client id"); log.info({ mqttUri: this.mqttUri, clientId }, "connecting to mqtt broker with client id");
this.client = mqtt.connect(this.mqttUri, { this.client = mqtt.connect(this.mqttUri, {
clientId, connectTimeout: 5000, reconnectPeriod: 5000, clientId, connectTimeout: 5000, reconnectPeriod: 5000,
@ -127,14 +128,14 @@ const handler = (test: RegExp) =>
}; };
class MqttSprinklersDevice extends s.SprinklersDevice { class MqttSprinklersDevice extends s.SprinklersDevice {
readonly apiClient: MqttApiClient; readonly apiClient: MqttRpcClient;
readonly prefix: string; readonly prefix: string;
handlers!: IHandlerEntry[]; handlers!: IHandlerEntry[];
private nextRequestId: number = Math.floor(Math.random() * 1000000000); private nextRequestId: number = Math.floor(Math.random() * 1000000000);
private responseCallbacks: Map<number, ResponseCallback> = new Map(); private responseCallbacks: Map<number, ResponseCallback> = new Map();
constructor(apiClient: MqttApiClient, prefix: string) { constructor(apiClient: MqttRpcClient, prefix: string) {
super(); super();
this.sectionConstructor = MqttSection; this.sectionConstructor = MqttSection;
this.sectionRunnerConstructor = MqttSectionRunner; this.sectionRunnerConstructor = MqttSectionRunner;

14
server/models/Database.ts

@ -1,6 +1,7 @@
import * as r from "rethinkdb"; import * as r from "rethinkdb";
import logger from "@common/logger"; import logger from "@common/logger";
import { SprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice";
import { User } from "./User"; import { User } from "./User";
export class Database { export class Database {
@ -43,6 +44,12 @@ export class Database {
if (tables.indexOf(User.tableName) === -1) { if (tables.indexOf(User.tableName) === -1) {
await User.createTable(this); 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, { const alex = new User(this, {
name: "Alex Mikhalev", name: "Alex Mikhalev",
username: "alex", username: "alex",
@ -53,5 +60,12 @@ export class Database {
const alex2 = await User.loadByUsername(this, "alex"); const alex2 = await User.loadByUsername(this, "alex");
logger.info("password valid: " + await alex2!.comparePassword("kakashka")); logger.info("password valid: " + await alex2!.comparePassword("kakashka"));
const device = new SprinklersDevice(this, {
name: "test",
});
await device.createOrUpdate();
device.addToUser
} }
} }

152
server/models/SprinklersDevice.ts

@ -0,0 +1,152 @@
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<ISprinklersDevice>) {
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<SprinklersDevice[]> {
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<SprinklersDevice | null> {
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<any>,
device.nth(0).update(data) as r.Expression<any>)
.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;
}
}

12
server/models/User.ts

@ -14,7 +14,7 @@ export interface IUser {
const HASH_ROUNDS = 10; const HASH_ROUNDS = 10;
export class User implements IUser { export class User implements IUser {
static readonly tableName = "users"; static readonly tableName = "Users";
id: string | undefined; id: string | undefined;
username: string = ""; username: string = "";
@ -72,9 +72,10 @@ export class User implements IUser {
async create() { async create() {
const data = serialize(this); const data = serialize(this);
delete data.id; delete data.id;
await this.table const result = await this.table
.insert(data) .insert(data)
.run(this.db.conn); .run(this.db.conn);
this.id = result.generated_keys[0];
} }
async createOrUpdate() { async createOrUpdate() {
@ -86,7 +87,12 @@ export class User implements IUser {
this.table.insert(data) as r.Expression<any>, this.table.insert(data) as r.Expression<any>,
user.nth(0).update(data) as r.Expression<any>) user.nth(0).update(data) as r.Expression<any>)
.run(this.db.conn); .run(this.db.conn);
return a.inserted > 0; if (a.inserted > 0) {
this.id = a.generated_keys[0];
return true;
} else {
return false;
}
} }
async setPassword(newPassword: string): Promise<void> { async setPassword(newPassword: string): Promise<void> {

3
server/models/index.ts

@ -0,0 +1,3 @@
export { User, IUser } from "./User";
export { SprinklersDevice, ISprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice";
export { Database } from "./Database";

4
server/state.ts

@ -3,7 +3,7 @@ import * as mqtt from "@common/sprinklersRpc/mqtt";
import { Database } from "./models/Database"; import { Database } from "./models/Database";
export class ServerState { export class ServerState {
mqttClient: mqtt.MqttApiClient; mqttClient: mqtt.MqttRpcClient;
database: Database; database: Database;
constructor() { constructor() {
@ -11,7 +11,7 @@ export class ServerState {
if (!mqttUrl) { if (!mqttUrl) {
throw new Error("Must specify a MQTT_URL to connect to"); throw new Error("Must specify a MQTT_URL to connect to");
} }
this.mqttClient = new mqtt.MqttApiClient(mqttUrl); this.mqttClient = new mqtt.MqttRpcClient(mqttUrl);
this.database = new Database(); this.database = new Database();
} }

Loading…
Cancel
Save