Started adding some device stuff for database
This commit is contained in:
parent
757141c3cb
commit
9aadf8e023
@ -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}/>
|
<Icon name={iconName}/>
|
||||||
|
@ -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) {
|
@ -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");
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
Normal file
152
server/models/SprinklersDevice.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
Normal file
3
server/models/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { User, IUser } from "./User";
|
||||||
|
export { SprinklersDevice, ISprinklersDevice, UserSprinklersDevice } from "./SprinklersDevice";
|
||||||
|
export { Database } from "./Database";
|
@ -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…
x
Reference in New Issue
Block a user