Alex Mikhalev
6 years ago
104 changed files with 4932 additions and 4119 deletions
@ -1,13 +1,14 @@
@@ -1,13 +1,14 @@
|
||||
.scheduleView { |
||||
>.field, >.fields { |
||||
>label { |
||||
width: 2rem !important; |
||||
} |
||||
> .field, |
||||
> .fields { |
||||
> label { |
||||
width: 2rem !important; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.scheduleTimes { |
||||
input { |
||||
margin: 0 .5rem; |
||||
} |
||||
input { |
||||
margin: 0 0.5rem; |
||||
} |
||||
} |
||||
|
@ -1,26 +1,32 @@
@@ -1,26 +1,32 @@
|
||||
import { ErrorCode, toHttpStatus } from "@common/ErrorCode"; |
||||
|
||||
export default class ApiError extends Error { |
||||
name = "ApiError"; |
||||
statusCode: number; |
||||
code: ErrorCode; |
||||
data: any; |
||||
name = "ApiError"; |
||||
statusCode: number; |
||||
code: ErrorCode; |
||||
data: any; |
||||
|
||||
constructor(message: string, code: ErrorCode = ErrorCode.BadRequest, data: any = {}) { |
||||
super(message); |
||||
this.statusCode = toHttpStatus(code); |
||||
this.code = code; |
||||
// tslint:disable-next-line:prefer-conditional-expression
|
||||
if (data instanceof Error) { |
||||
this.data = data.toString(); |
||||
} else { |
||||
this.data = data; |
||||
} |
||||
constructor( |
||||
message: string, |
||||
code: ErrorCode = ErrorCode.BadRequest, |
||||
data: any = {} |
||||
) { |
||||
super(message); |
||||
this.statusCode = toHttpStatus(code); |
||||
this.code = code; |
||||
// tslint:disable-next-line:prefer-conditional-expression
|
||||
if (data instanceof Error) { |
||||
this.data = data.toString(); |
||||
} else { |
||||
this.data = data; |
||||
} |
||||
} |
||||
|
||||
toJSON() { |
||||
return { |
||||
message: this.message, code: this.code, data: this.data, |
||||
}; |
||||
} |
||||
toJSON() { |
||||
return { |
||||
message: this.message, |
||||
code: this.code, |
||||
data: this.data |
||||
}; |
||||
} |
||||
} |
||||
|
@ -1,41 +1,41 @@
@@ -1,41 +1,41 @@
|
||||
export class Duration { |
||||
static fromSeconds(seconds: number): Duration { |
||||
return new Duration(Math.floor(seconds / 60), seconds % 60); |
||||
} |
||||
static fromSeconds(seconds: number): Duration { |
||||
return new Duration(Math.floor(seconds / 60), seconds % 60); |
||||
} |
||||
|
||||
minutes: number = 0; |
||||
seconds: number = 0; |
||||
minutes: number = 0; |
||||
seconds: number = 0; |
||||
|
||||
constructor(minutes: number = 0, seconds: number = 0) { |
||||
this.minutes = minutes; |
||||
this.seconds = seconds; |
||||
} |
||||
constructor(minutes: number = 0, seconds: number = 0) { |
||||
this.minutes = minutes; |
||||
this.seconds = seconds; |
||||
} |
||||
|
||||
toSeconds(): number { |
||||
return this.minutes * 60 + this.seconds; |
||||
} |
||||
toSeconds(): number { |
||||
return this.minutes * 60 + this.seconds; |
||||
} |
||||
|
||||
withSeconds(newSeconds: number): Duration { |
||||
let newMinutes = this.minutes; |
||||
if (newSeconds >= 60) { |
||||
newMinutes++; |
||||
newSeconds = 0; |
||||
} |
||||
if (newSeconds < 0) { |
||||
newMinutes = Math.max(0, newMinutes - 1); |
||||
newSeconds = 59; |
||||
} |
||||
return new Duration(newMinutes, newSeconds); |
||||
withSeconds(newSeconds: number): Duration { |
||||
let newMinutes = this.minutes; |
||||
if (newSeconds >= 60) { |
||||
newMinutes++; |
||||
newSeconds = 0; |
||||
} |
||||
|
||||
withMinutes(newMinutes: number): Duration { |
||||
if (newMinutes < 0) { |
||||
newMinutes = 0; |
||||
} |
||||
return new Duration(newMinutes, this.seconds); |
||||
if (newSeconds < 0) { |
||||
newMinutes = Math.max(0, newMinutes - 1); |
||||
newSeconds = 59; |
||||
} |
||||
return new Duration(newMinutes, newSeconds); |
||||
} |
||||
|
||||
toString(): string { |
||||
return `${this.minutes}M ${this.seconds.toFixed(1)}S`; |
||||
withMinutes(newMinutes: number): Duration { |
||||
if (newMinutes < 0) { |
||||
newMinutes = 0; |
||||
} |
||||
return new Duration(newMinutes, this.seconds); |
||||
} |
||||
|
||||
toString(): string { |
||||
return `${this.minutes}M ${this.seconds.toFixed(1)}S`; |
||||
} |
||||
} |
||||
|
@ -1,41 +1,41 @@
@@ -1,41 +1,41 @@
|
||||
export enum ErrorCode { |
||||
BadRequest = 100, |
||||
NotSpecified = 101, |
||||
Parse = 102, |
||||
Range = 103, |
||||
InvalidData = 104, |
||||
BadToken = 105, |
||||
Unauthorized = 106, |
||||
NoPermission = 107, |
||||
NotImplemented = 108, |
||||
NotFound = 109, |
||||
Internal = 200, |
||||
Timeout = 300, |
||||
ServerDisconnected = 301, |
||||
BrokerDisconnected = 302, |
||||
BadRequest = 100, |
||||
NotSpecified = 101, |
||||
Parse = 102, |
||||
Range = 103, |
||||
InvalidData = 104, |
||||
BadToken = 105, |
||||
Unauthorized = 106, |
||||
NoPermission = 107, |
||||
NotImplemented = 108, |
||||
NotFound = 109, |
||||
Internal = 200, |
||||
Timeout = 300, |
||||
ServerDisconnected = 301, |
||||
BrokerDisconnected = 302 |
||||
} |
||||
|
||||
export function toHttpStatus(errorCode: ErrorCode): number { |
||||
switch (errorCode) { |
||||
case ErrorCode.BadRequest: |
||||
case ErrorCode.NotSpecified: |
||||
case ErrorCode.Parse: |
||||
case ErrorCode.Range: |
||||
case ErrorCode.InvalidData: |
||||
return 400; // Bad request
|
||||
case ErrorCode.Unauthorized: |
||||
case ErrorCode.BadToken: |
||||
return 401; // Unauthorized
|
||||
case ErrorCode.NoPermission: |
||||
return 403; // Forbidden
|
||||
case ErrorCode.NotFound: |
||||
return 404; |
||||
case ErrorCode.NotImplemented: |
||||
return 501; |
||||
case ErrorCode.Internal: |
||||
case ErrorCode.ServerDisconnected: |
||||
case ErrorCode.BrokerDisconnected: |
||||
default: |
||||
return 500; |
||||
} |
||||
switch (errorCode) { |
||||
case ErrorCode.BadRequest: |
||||
case ErrorCode.NotSpecified: |
||||
case ErrorCode.Parse: |
||||
case ErrorCode.Range: |
||||
case ErrorCode.InvalidData: |
||||
return 400; // Bad request
|
||||
case ErrorCode.Unauthorized: |
||||
case ErrorCode.BadToken: |
||||
return 401; // Unauthorized
|
||||
case ErrorCode.NoPermission: |
||||
return 403; // Forbidden
|
||||
case ErrorCode.NotFound: |
||||
return 404; |
||||
case ErrorCode.NotImplemented: |
||||
return 501; |
||||
case ErrorCode.Internal: |
||||
case ErrorCode.ServerDisconnected: |
||||
case ErrorCode.BrokerDisconnected: |
||||
default: |
||||
return 500; |
||||
} |
||||
} |
||||
|
@ -1,34 +1,39 @@
@@ -1,34 +1,39 @@
|
||||
export interface BaseClaims { |
||||
iss: string; |
||||
exp?: number; |
||||
iss: string; |
||||
exp?: number; |
||||
} |
||||
|
||||
export interface AccessToken { |
||||
type: "access"; |
||||
aud: number; |
||||
name: string; |
||||
type: "access"; |
||||
aud: number; |
||||
name: string; |
||||
} |
||||
|
||||
export interface RefreshToken { |
||||
type: "refresh"; |
||||
aud: number; |
||||
name: string; |
||||
type: "refresh"; |
||||
aud: number; |
||||
name: string; |
||||
} |
||||
|
||||
export interface DeviceRegistrationToken { |
||||
type: "device_reg"; |
||||
type: "device_reg"; |
||||
} |
||||
|
||||
export interface DeviceToken { |
||||
type: "device"; |
||||
aud: string; |
||||
id: number; |
||||
type: "device"; |
||||
aud: string; |
||||
id: number; |
||||
} |
||||
|
||||
export interface SuperuserToken { |
||||
type: "superuser"; |
||||
type: "superuser"; |
||||
} |
||||
|
||||
export type TokenClaimTypes = AccessToken | RefreshToken | DeviceRegistrationToken | DeviceToken | SuperuserToken; |
||||
export type TokenClaimTypes = |
||||
| AccessToken |
||||
| RefreshToken |
||||
| DeviceRegistrationToken |
||||
| DeviceToken |
||||
| SuperuserToken; |
||||
|
||||
export type TokenClaims = TokenClaimTypes & BaseClaims; |
||||
|
@ -1,31 +1,33 @@
@@ -1,31 +1,33 @@
|
||||
export interface TokenGrantPasswordRequest { |
||||
grant_type: "password"; |
||||
username: string; |
||||
password: string; |
||||
grant_type: "password"; |
||||
username: string; |
||||
password: string; |
||||
} |
||||
|
||||
export interface TokenGrantRefreshRequest { |
||||
grant_type: "refresh"; |
||||
refresh_token: string; |
||||
grant_type: "refresh"; |
||||
refresh_token: string; |
||||
} |
||||
|
||||
export type TokenGrantRequest = TokenGrantPasswordRequest | TokenGrantRefreshRequest; |
||||
export type TokenGrantRequest = |
||||
| TokenGrantPasswordRequest |
||||
| TokenGrantRefreshRequest; |
||||
|
||||
export interface TokenGrantResponse { |
||||
access_token: string; |
||||
refresh_token: string; |
||||
access_token: string; |
||||
refresh_token: string; |
||||
} |
||||
|
||||
export interface IUser { |
||||
id: number; |
||||
username: string; |
||||
name: string; |
||||
devices: ISprinklersDevice[] | undefined; |
||||
id: number; |
||||
username: string; |
||||
name: string; |
||||
devices: ISprinklersDevice[] | undefined; |
||||
} |
||||
|
||||
export interface ISprinklersDevice { |
||||
id: number; |
||||
deviceId: string | null; |
||||
name: string; |
||||
users: IUser[] | undefined; |
||||
id: number; |
||||
deviceId: string | null; |
||||
name: string; |
||||
users: IUser[] | undefined; |
||||
} |
||||
|
@ -1,73 +1,81 @@
@@ -1,73 +1,81 @@
|
||||
import { computed, observable } from "mobx"; |
||||
|
||||
export class ConnectionState { |
||||
/** |
||||
* Represents if a client is connected to the sprinklers3 server (eg. via websocket) |
||||
* Can be null if there is no client involved |
||||
*/ |
||||
@observable clientToServer: boolean | null = null; |
||||
/** |
||||
* Represents if a client is connected to the sprinklers3 server (eg. via websocket) |
||||
* Can be null if there is no client involved |
||||
*/ |
||||
@observable |
||||
clientToServer: boolean | null = null; |
||||
|
||||
/** |
||||
* Represents if the sprinklers3 server is connected to the broker (eg. via mqtt) |
||||
* Can be null if there is no broker involved |
||||
*/ |
||||
@observable serverToBroker: boolean | null = null; |
||||
/** |
||||
* Represents if the sprinklers3 server is connected to the broker (eg. via mqtt) |
||||
* Can be null if there is no broker involved |
||||
*/ |
||||
@observable |
||||
serverToBroker: boolean | null = null; |
||||
|
||||
/** |
||||
* Represents if the device is connected to the broker and we can communicate with it (eg. via mqtt) |
||||
* Can be null if there is no device involved |
||||
*/ |
||||
@observable brokerToDevice: boolean | null = null; |
||||
/** |
||||
* Represents if the device is connected to the broker and we can communicate with it (eg. via mqtt) |
||||
* Can be null if there is no device involved |
||||
*/ |
||||
@observable |
||||
brokerToDevice: boolean | null = null; |
||||
|
||||
/** |
||||
* Represents if whoever is trying to access this device has permission to access it. |
||||
* Is null if there is no concept of access involved. |
||||
*/ |
||||
@observable hasPermission: boolean | null = null; |
||||
/** |
||||
* Represents if whoever is trying to access this device has permission to access it. |
||||
* Is null if there is no concept of access involved. |
||||
*/ |
||||
@observable |
||||
hasPermission: boolean | null = null; |
||||
|
||||
@computed get noPermission() { |
||||
return this.hasPermission === false; |
||||
} |
||||
@computed |
||||
get noPermission() { |
||||
return this.hasPermission === false; |
||||
} |
||||
|
||||
@computed get isAvailable(): boolean { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.brokerToDevice != null) { |
||||
return true; |
||||
} |
||||
if (this.serverToBroker != null) { |
||||
return this.serverToBroker; |
||||
} |
||||
if (this.clientToServer != null) { |
||||
return this.clientToServer; |
||||
} |
||||
return false; |
||||
@computed |
||||
get isAvailable(): boolean { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.brokerToDevice != null) { |
||||
return true; |
||||
} |
||||
if (this.serverToBroker != null) { |
||||
return this.serverToBroker; |
||||
} |
||||
if (this.clientToServer != null) { |
||||
return this.clientToServer; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@computed get isDeviceConnected(): boolean | null { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.serverToBroker === false || this.clientToServer === false) { |
||||
return null; |
||||
} |
||||
if (this.brokerToDevice != null) { |
||||
return this.brokerToDevice; |
||||
} |
||||
return null; |
||||
@computed |
||||
get isDeviceConnected(): boolean | null { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.serverToBroker === false || this.clientToServer === false) { |
||||
return null; |
||||
} |
||||
if (this.brokerToDevice != null) { |
||||
return this.brokerToDevice; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@computed get isServerConnected(): boolean | null { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.serverToBroker != null) { |
||||
return this.serverToBroker; |
||||
} |
||||
if (this.clientToServer != null) { |
||||
return this.brokerToDevice; |
||||
} |
||||
return null; |
||||
@computed |
||||
get isServerConnected(): boolean | null { |
||||
if (this.hasPermission === false) { |
||||
return false; |
||||
} |
||||
if (this.serverToBroker != null) { |
||||
return this.serverToBroker; |
||||
} |
||||
if (this.clientToServer != null) { |
||||
return this.brokerToDevice; |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
@ -1,40 +1,38 @@
@@ -1,40 +1,38 @@
|
||||
import { |
||||
ModelSchema, primitive, PropSchema, |
||||
} from "serializr"; |
||||
import { ModelSchema, primitive, PropSchema } from "serializr"; |
||||
import * as s from ".."; |
||||
|
||||
export const duration: PropSchema = primitive(); |
||||
|
||||
export const date: PropSchema = { |
||||
serializer: (jsDate: Date | null) => jsDate != null ? |
||||
jsDate.toISOString() : null, |
||||
deserializer: (json: any, done) => { |
||||
if (json === null) { |
||||
return done(null, null); |
||||
} |
||||
try { |
||||
done(null, new Date(json)); |
||||
} catch (e) { |
||||
done(e, undefined); |
||||
} |
||||
}, |
||||
serializer: (jsDate: Date | null) => |
||||
jsDate != null ? jsDate.toISOString() : null, |
||||
deserializer: (json: any, done) => { |
||||
if (json === null) { |
||||
return done(null, null); |
||||
} |
||||
try { |
||||
done(null, new Date(json)); |
||||
} catch (e) { |
||||
done(e, undefined); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
export const dateOfYear: ModelSchema<s.DateOfYear> = { |
||||
factory: () => new s.DateOfYear(), |
||||
props: { |
||||
year: primitive(), |
||||
month: primitive(), // this only works if it is represented as a # from 0-12
|
||||
day: primitive(), |
||||
}, |
||||
factory: () => new s.DateOfYear(), |
||||
props: { |
||||
year: primitive(), |
||||
month: primitive(), // this only works if it is represented as a # from 0-12
|
||||
day: primitive() |
||||
} |
||||
}; |
||||
|
||||
export const timeOfDay: ModelSchema<s.TimeOfDay> = { |
||||
factory: () => new s.TimeOfDay(), |
||||
props: { |
||||
hour: primitive(), |
||||
minute: primitive(), |
||||
second: primitive(), |
||||
millisecond: primitive(), |
||||
}, |
||||
factory: () => new s.TimeOfDay(), |
||||
props: { |
||||
hour: primitive(), |
||||
minute: primitive(), |
||||
second: primitive(), |
||||
millisecond: primitive() |
||||
} |
||||
}; |
||||
|
@ -1,65 +1,75 @@
@@ -1,65 +1,75 @@
|
||||
import { primitive, PropSchema } from "serializr"; |
||||
|
||||
function invariant(cond: boolean, message?: string) { |
||||
if (!cond) { |
||||
throw new Error("[serializr] " + (message || "Illegal ServerState")); |
||||
} |
||||
if (!cond) { |
||||
throw new Error("[serializr] " + (message || "Illegal ServerState")); |
||||
} |
||||
} |
||||
|
||||
function isPropSchema(thing: any) { |
||||
return thing && thing.serializer && thing.deserializer; |
||||
return thing && thing.serializer && thing.deserializer; |
||||
} |
||||
|
||||
function isAliasedPropSchema(propSchema: any) { |
||||
return typeof propSchema === "object" && !!propSchema.jsonname; |
||||
return typeof propSchema === "object" && !!propSchema.jsonname; |
||||
} |
||||
|
||||
function parallel(ar: any[], processor: (item: any, done: any) => void, cb: any) { |
||||
if (ar.length === 0) { |
||||
return void cb(null, []); |
||||
function parallel( |
||||
ar: any[], |
||||
processor: (item: any, done: any) => void, |
||||
cb: any |
||||
) { |
||||
if (ar.length === 0) { |
||||
return void cb(null, []); |
||||
} |
||||
let left = ar.length; |
||||
const resultArray: any[] = []; |
||||
let failed = false; |
||||
const processorCb = (idx: number, err: any, result: any) => { |
||||
if (err) { |
||||
if (!failed) { |
||||
failed = true; |
||||
cb(err); |
||||
} |
||||
} else if (!failed) { |
||||
resultArray[idx] = result; |
||||
if (--left === 0) { |
||||
cb(null, resultArray); |
||||
} |
||||
} |
||||
let left = ar.length; |
||||
const resultArray: any[] = []; |
||||
let failed = false; |
||||
const processorCb = (idx: number, err: any, result: any) => { |
||||
if (err) { |
||||
if (!failed) { |
||||
failed = true; |
||||
cb(err); |
||||
} |
||||
} else if (!failed) { |
||||
resultArray[idx] = result; |
||||
if (--left === 0) { |
||||
cb(null, resultArray); |
||||
} |
||||
} |
||||
}; |
||||
ar.forEach((value, idx) => processor(value, processorCb.bind(null, idx))); |
||||
}; |
||||
ar.forEach((value, idx) => processor(value, processorCb.bind(null, idx))); |
||||
} |
||||
|
||||
export default function list(propSchema: PropSchema): PropSchema { |
||||
propSchema = propSchema || primitive(); |
||||
invariant(isPropSchema(propSchema), "expected prop schema as first argument"); |
||||
invariant(!isAliasedPropSchema(propSchema), "provided prop is aliased, please put aliases first"); |
||||
return { |
||||
serializer(ar) { |
||||
invariant(ar && typeof ar.length === "number" && typeof ar.map === "function", |
||||
"expected array (like) object"); |
||||
return ar.map(propSchema.serializer); |
||||
}, |
||||
deserializer(jsonArray, done, context) { |
||||
if (jsonArray === null) { // sometimes go will return null in place of empty array
|
||||
return void done(null, []); |
||||
} |
||||
if (!Array.isArray(jsonArray)) { |
||||
return void done("[serializr] expected JSON array", null); |
||||
} |
||||
parallel( |
||||
jsonArray, |
||||
(item: any, itemDone: (err: any, targetPropertyValue: any) => void) => |
||||
propSchema.deserializer(item, itemDone, context, undefined), |
||||
done, |
||||
); |
||||
}, |
||||
}; |
||||
propSchema = propSchema || primitive(); |
||||
invariant(isPropSchema(propSchema), "expected prop schema as first argument"); |
||||
invariant( |
||||
!isAliasedPropSchema(propSchema), |
||||
"provided prop is aliased, please put aliases first" |
||||
); |
||||
return { |
||||
serializer(ar) { |
||||
invariant( |
||||
ar && typeof ar.length === "number" && typeof ar.map === "function", |
||||
"expected array (like) object" |
||||
); |
||||
return ar.map(propSchema.serializer); |
||||
}, |
||||
deserializer(jsonArray, done, context) { |
||||
if (jsonArray === null) { |
||||
// sometimes go will return null in place of empty array
|
||||
return void done(null, []); |
||||
} |
||||
if (!Array.isArray(jsonArray)) { |
||||
return void done("[serializr] expected JSON array", null); |
||||
} |
||||
parallel( |
||||
jsonArray, |
||||
(item: any, itemDone: (err: any, targetPropertyValue: any) => void) => |
||||
propSchema.deserializer(item, itemDone, context, undefined), |
||||
done |
||||
); |
||||
} |
||||
}; |
||||
} |
||||
|
@ -1,69 +1,89 @@
@@ -1,69 +1,89 @@
|
||||
import { createSimpleSchema, deserialize, ModelSchema, primitive, serialize } from "serializr"; |
||||
import { |
||||
createSimpleSchema, |
||||
deserialize, |
||||
ModelSchema, |
||||
primitive, |
||||
serialize |
||||
} from "serializr"; |
||||
|
||||
import * as requests from "@common/sprinklersRpc/deviceRequests"; |
||||
import * as common from "./common"; |
||||
|
||||
export const withType: ModelSchema<requests.WithType> = createSimpleSchema({ |
||||
type: primitive(), |
||||
type: primitive() |
||||
}); |
||||
|
||||
export const withProgram: ModelSchema<requests.WithProgram> = createSimpleSchema({ |
||||
...withType.props, |
||||
programId: primitive(), |
||||
export const withProgram: ModelSchema< |
||||
requests.WithProgram |
||||
> = createSimpleSchema({ |
||||
...withType.props, |
||||
programId: primitive() |
||||
}); |
||||
|
||||
export const withSection: ModelSchema<requests.WithSection> = createSimpleSchema({ |
||||
...withType.props, |
||||
sectionId: primitive(), |
||||
export const withSection: ModelSchema< |
||||
requests.WithSection |
||||
> = createSimpleSchema({ |
||||
...withType.props, |
||||
sectionId: primitive() |
||||
}); |
||||
|
||||
export const updateProgram: ModelSchema<requests.UpdateProgramData> = createSimpleSchema({ |
||||
...withProgram.props, |
||||
data: { |
||||
serializer: (data) => data, |
||||
deserializer: (json, done) => { done(null, json); }, |
||||
}, |
||||
export const updateProgram: ModelSchema< |
||||
requests.UpdateProgramData |
||||
> = createSimpleSchema({ |
||||
...withProgram.props, |
||||
data: { |
||||
serializer: data => data, |
||||
deserializer: (json, done) => { |
||||
done(null, json); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
export const runSection: ModelSchema<requests.RunSectionData> = createSimpleSchema({ |
||||
...withSection.props, |
||||
duration: common.duration, |
||||
export const runSection: ModelSchema< |
||||
requests.RunSectionData |
||||
> = createSimpleSchema({ |
||||
...withSection.props, |
||||
duration: common.duration |
||||
}); |
||||
|
||||
export const cancelSectionRunId: ModelSchema<requests.CancelSectionRunIdData> = createSimpleSchema({ |
||||
...withType.props, |
||||
runId: primitive(), |
||||
export const cancelSectionRunId: ModelSchema< |
||||
requests.CancelSectionRunIdData |
||||
> = createSimpleSchema({ |
||||
...withType.props, |
||||
runId: primitive() |
||||
}); |
||||
|
||||
export const pauseSectionRunner: ModelSchema<requests.PauseSectionRunnerData> = createSimpleSchema({ |
||||
...withType.props, |
||||
paused: primitive(), |
||||
export const pauseSectionRunner: ModelSchema< |
||||
requests.PauseSectionRunnerData |
||||
> = createSimpleSchema({ |
||||
...withType.props, |
||||
paused: primitive() |
||||
}); |
||||
|
||||
export function getRequestSchema(request: requests.WithType): ModelSchema<any> { |
||||
switch (request.type as requests.RequestType) { |
||||
case "runProgram": |
||||
case "cancelProgram": |
||||
return withProgram; |
||||
case "updateProgram": |
||||
return updateProgram; |
||||
case "runSection": |
||||
return runSection; |
||||
case "cancelSection": |
||||
return withSection; |
||||
case "cancelSectionRunId": |
||||
return cancelSectionRunId; |
||||
case "pauseSectionRunner": |
||||
return pauseSectionRunner; |
||||
default: |
||||
throw new Error(`Cannot serialize request with type "${request.type}"`); |
||||
} |
||||
switch (request.type as requests.RequestType) { |
||||
case "runProgram": |
||||
case "cancelProgram": |
||||
return withProgram; |
||||
case "updateProgram": |
||||
return updateProgram; |
||||
case "runSection": |
||||
return runSection; |
||||
case "cancelSection": |
||||
return withSection; |
||||
case "cancelSectionRunId": |
||||
return cancelSectionRunId; |
||||
case "pauseSectionRunner": |
||||
return pauseSectionRunner; |
||||
default: |
||||
throw new Error(`Cannot serialize request with type "${request.type}"`); |
||||
} |
||||
} |
||||
|
||||
export function seralizeRequest(request: requests.Request): any { |
||||
return serialize(getRequestSchema(request), request); |
||||
return serialize(getRequestSchema(request), request); |
||||
} |
||||
|
||||
export function deserializeRequest(json: any): requests.Request { |
||||
return deserialize(getRequestSchema(json), json); |
||||
return deserialize(getRequestSchema(json), json); |
||||
} |
||||
|
@ -1,26 +1,30 @@
@@ -1,26 +1,30 @@
|
||||
export function checkedIndexOf<T>(o: T | number, arr: T[], type: string = "object"): number { |
||||
let idx: number; |
||||
if (typeof o === "number") { |
||||
idx = o; |
||||
} else if (typeof (o as any).id === "number") { |
||||
idx = (o as any).id; |
||||
} else { |
||||
idx = arr.indexOf(o); |
||||
} |
||||
if (idx < 0 || idx > arr.length) { |
||||
throw new Error(`Invalid ${type} specified: ${o}`); |
||||
} |
||||
return idx; |
||||
export function checkedIndexOf<T>( |
||||
o: T | number, |
||||
arr: T[], |
||||
type: string = "object" |
||||
): number { |
||||
let idx: number; |
||||
if (typeof o === "number") { |
||||
idx = o; |
||||
} else if (typeof (o as any).id === "number") { |
||||
idx = (o as any).id; |
||||
} else { |
||||
idx = arr.indexOf(o); |
||||
} |
||||
if (idx < 0 || idx > arr.length) { |
||||
throw new Error(`Invalid ${type} specified: ${o}`); |
||||
} |
||||
return idx; |
||||
} |
||||
|
||||
export function getRandomId() { |
||||
return Math.floor(Math.random() * 1000000000); |
||||
return Math.floor(Math.random() * 1000000000); |
||||
} |
||||
|
||||
export function applyMixins(derivedCtor: any, baseCtors: any[]) { |
||||
baseCtors.forEach((baseCtor) => { |
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { |
||||
derivedCtor.prototype[name] = baseCtor.prototype[name]; |
||||
}); |
||||
baseCtors.forEach(baseCtor => { |
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { |
||||
derivedCtor.prototype[name] = baseCtor.prototype[name]; |
||||
}); |
||||
}); |
||||
} |
||||
|
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
||||
import log from "@common/logger"; |
||||
Object.assign(log, { |
||||
name: "sprinklers3/server", |
||||
level: process.env.LOG_LEVEL || "debug", |
||||
name: "sprinklers3/server", |
||||
level: process.env.LOG_LEVEL || "debug" |
||||
}); |
||||
|
@ -1,45 +1,51 @@
@@ -1,45 +1,51 @@
|
||||
import * as bcrypt from "bcrypt"; |
||||
import { omit } from "lodash"; |
||||
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; |
||||
import { |
||||
Column, |
||||
Entity, |
||||
JoinTable, |
||||
ManyToMany, |
||||
PrimaryGeneratedColumn |
||||
} from "typeorm"; |
||||
|
||||
import { IUser } from "@common/httpApi"; |
||||
import { SprinklersDevice} from "./SprinklersDevice"; |
||||
import { SprinklersDevice } from "./SprinklersDevice"; |
||||
|
||||
const HASH_ROUNDS = 1; |
||||
|
||||
@Entity() |
||||
export class User implements IUser { |
||||
@PrimaryGeneratedColumn() |
||||
id!: number; |
||||
@PrimaryGeneratedColumn() |
||||
id!: number; |
||||
|
||||
@Column({ unique: true }) |
||||
username: string = ""; |
||||
@Column({ unique: true }) |
||||
username: string = ""; |
||||
|
||||
@Column() |
||||
name: string = ""; |
||||
@Column() |
||||
name: string = ""; |
||||
|
||||
@Column() |
||||
passwordHash: string = ""; |
||||
@Column() |
||||
passwordHash: string = ""; |
||||
|
||||
@ManyToMany((type) => SprinklersDevice) |
||||
@JoinTable({ name: "user_sprinklers_device" }) |
||||
devices: SprinklersDevice[] | undefined; |
||||
@ManyToMany(type => SprinklersDevice) |
||||
@JoinTable({ name: "user_sprinklers_device" }) |
||||
devices: SprinklersDevice[] | undefined; |
||||
|
||||
constructor(data?: Partial<User>) { |
||||
if (data) { |
||||
Object.assign(this, data); |
||||
} |
||||
constructor(data?: Partial<User>) { |
||||
if (data) { |
||||
Object.assign(this, data); |
||||
} |
||||
} |
||||
|
||||
async setPassword(newPassword: string): Promise<void> { |
||||
this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS); |
||||
} |
||||
async setPassword(newPassword: string): Promise<void> { |
||||
this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS); |
||||
} |
||||
|
||||
async comparePassword(password: string): Promise<boolean> { |
||||
return bcrypt.compare(password, this.passwordHash); |
||||
} |
||||
async comparePassword(password: string): Promise<boolean> { |
||||
return bcrypt.compare(password, this.passwordHash); |
||||
} |
||||
|
||||
toJSON() { |
||||
return omit(this, "passwordHash"); |
||||
} |
||||
toJSON() { |
||||
return omit(this, "passwordHash"); |
||||
} |
||||
} |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue