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