Alex Mikhalev
7 years ago
11 changed files with 321 additions and 36 deletions
@ -1,22 +0,0 @@
@@ -1,22 +0,0 @@
|
||||
import * as express from "express"; |
||||
|
||||
import * as schema from "@common/sprinklers/schema"; |
||||
import {serialize} from "serializr"; |
||||
import { ServerState } from "../state"; |
||||
import logger from "./logger"; |
||||
import serveApp from "./serveApp"; |
||||
|
||||
export function createApp(state: ServerState) { |
||||
const app = express(); |
||||
|
||||
app.use(logger); |
||||
|
||||
app.get("/api/grinklers", (req, res) => { |
||||
const j = serialize(schema.sprinklersDevice, state.device); |
||||
res.send(j); |
||||
}); |
||||
|
||||
serveApp(app); |
||||
|
||||
return app; |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
import * as bodyParser from "body-parser"; |
||||
import * as express from "express"; |
||||
import { serialize, serializeAll } from "serializr"; |
||||
|
||||
import * as schema from "@common/sprinklers/schema"; |
||||
import { ServerState } from "../state"; |
||||
import logger from "./logger"; |
||||
import serveApp from "./serveApp"; |
||||
|
||||
import log from "@common/logger"; |
||||
|
||||
import { User } from "../models/User"; |
||||
|
||||
export function createApp(state: ServerState) { |
||||
const app = express(); |
||||
|
||||
app.use(logger); |
||||
app.use(bodyParser.json()); |
||||
|
||||
app.get("/api/grinklers", (req, res) => { |
||||
const j = serialize(schema.sprinklersDevice, state.device); |
||||
res.send(j); |
||||
}); |
||||
|
||||
app.get("/api/users", (req, res) => { |
||||
User.loadAll(state.database) |
||||
.then((users) => { |
||||
res.json({ |
||||
data: users, |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
app.get("/api/users/:username", (req, res, next) => { |
||||
User.loadByUsername(state.database, req.params.username) |
||||
.then((user) => { |
||||
res.json({ |
||||
data: user, |
||||
}); |
||||
}) |
||||
.catch(next); |
||||
}); |
||||
|
||||
app.post("/api/authenticate", (req, res, next) => { |
||||
const body = req.body; |
||||
log.info({body}, "/api/authenticate: " + req.body); |
||||
if (!body || !body.username || !body.password) { |
||||
return next(new Error("must specify username and password")); |
||||
} |
||||
User.loadByUsername(state.database, body.username) |
||||
.then((user) => { |
||||
if (!user) { |
||||
throw new Error("user does not exist"); |
||||
} |
||||
return user.comparePassword(body.password); |
||||
}).then((passwordMatches) => { |
||||
res.json({ |
||||
passwordMatches, |
||||
}); |
||||
}).catch(next); |
||||
}); |
||||
|
||||
serveApp(app); |
||||
|
||||
return app; |
||||
} |
@ -1,11 +1,14 @@
@@ -1,11 +1,14 @@
|
||||
import { Express } from "express"; |
||||
import * as path from "path"; |
||||
import * as serveStatic from "serve-static"; |
||||
|
||||
import * as paths from "paths"; |
||||
|
||||
const index = path.join(paths.publicDir, "index.html"); |
||||
|
||||
export default function serveApp(app: Express) { |
||||
app.use(serveStatic(paths.appBuildDir)); |
||||
app.get("/*", (req, res) => { |
||||
res.sendFile(path.join(paths.publicDir, "index.html")); |
||||
res.sendFile(index); |
||||
}); |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
import * as r from "rethinkdb"; |
||||
|
||||
import { User } from "./User"; |
||||
import logger from "@common/logger"; |
||||
|
||||
export class Database { |
||||
static readonly databaseName = "sprinklers3"; |
||||
|
||||
db: r.Db; |
||||
private _conn: r.Connection | null = null; |
||||
|
||||
get conn(): r.Connection { |
||||
if (this._conn == null) { |
||||
throw new Error("Not connected to rethinkDB"); |
||||
} |
||||
return this._conn; |
||||
} |
||||
|
||||
constructor() { |
||||
this.db = r.db(Database.databaseName); |
||||
} |
||||
|
||||
async connect() { |
||||
this._conn = await r.connect("localhost"); |
||||
} |
||||
|
||||
async disconnect() { |
||||
if (this._conn) { |
||||
return this._conn.close(); |
||||
} |
||||
} |
||||
|
||||
async createAll() { |
||||
const dbs = await r.dbList().run(this.conn); |
||||
if (dbs.indexOf(Database.databaseName) === -1) { |
||||
await r.dbCreate(Database.databaseName).run(this.conn); |
||||
} |
||||
await this.createTables(); |
||||
} |
||||
|
||||
async createTables() { |
||||
const tables = await this.db.tableList().run(this.conn); |
||||
if (tables.indexOf(User.tableName) === -1) { |
||||
await User.createTable(this); |
||||
} |
||||
const alex = new User(this, { |
||||
name: "Alex Mikhalev", |
||||
username: "alex", |
||||
}); |
||||
await alex.setPassword("kakashka"); |
||||
const created = await alex.createOrUpdate(); |
||||
logger.info((created ? "created" : "updated") + " user alex"); |
||||
|
||||
const alex2 = await User.loadByUsername(this, "alex"); |
||||
logger.info("password valid: " + await alex2!.comparePassword("kakashka")); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
import * as r from "rethinkdb"; |
||||
import { createModelSchema, deserialize, primitive, serialize, update } from "serializr"; |
||||
import { Database } from "./Database"; |
||||
import * as bcrypt from "bcrypt"; |
||||
|
||||
export interface IUser { |
||||
id: string | undefined; |
||||
username: string; |
||||
name: string; |
||||
passwordHash: string; |
||||
} |
||||
|
||||
const HASH_ROUNDS = 10; |
||||
|
||||
export class User implements IUser { |
||||
static readonly tableName = "users"; |
||||
|
||||
id: string | undefined; |
||||
username: string = ""; |
||||
name: string = ""; |
||||
passwordHash: string = ""; |
||||
|
||||
private db: Database; |
||||
|
||||
private get _db() { |
||||
return this.db.db; |
||||
} |
||||
|
||||
private get table() { |
||||
return this.db.db.table(User.tableName); |
||||
} |
||||
|
||||
constructor(db: Database, data?: Partial<IUser>) { |
||||
this.db = db; |
||||
if (data) { |
||||
update(this, data); |
||||
} |
||||
} |
||||
|
||||
static async createTable(db: Database) { |
||||
await db.db.tableCreate(User.tableName).run(db.conn); |
||||
await db.db.table(User.tableName).indexCreate("username").run(db.conn); |
||||
} |
||||
|
||||
static async loadAll(db: Database): Promise<User[]> { |
||||
const cursor = await db.db.table(User.tableName) |
||||
.run(db.conn); |
||||
const users = await cursor.toArray(); |
||||
return users.map((data) => { |
||||
return new User(db, data); |
||||
}); |
||||
} |
||||
|
||||
static async loadByUsername(db: Database, username: string): Promise<User | null> { |
||||
const seq = await db.db.table(User.tableName) |
||||
.filter(r.row("username").eq(username)) |
||||
.run(db.conn); |
||||
const data = await seq.toArray(); |
||||
if (data.length === 0) { |
||||
return null; |
||||
} |
||||
return new User(db, data[0]); |
||||
} |
||||
|
||||
async create() { |
||||
const data = serialize(this); |
||||
delete data.id; |
||||
const a = this.table |
||||
.insert(data) |
||||
.run(this.db.conn); |
||||
} |
||||
|
||||
async createOrUpdate() { |
||||
const data = serialize(this); |
||||
delete data.id; |
||||
const user = this.table.filter(r.row("username").eq(this.username)); |
||||
const usernameDoesNotExist = user.isEmpty(); |
||||
const a: r.WriteResult = await r.branch(usernameDoesNotExist, |
||||
this.table.insert(data) as r.Expression<any>, |
||||
user.nth(0).update(data) as r.Expression<any>) |
||||
.run(this.db.conn); |
||||
return a.inserted > 0; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
toJSON(): any { |
||||
const data = serialize(this); |
||||
delete data.passwordHash; |
||||
return data; |
||||
} |
||||
} |
||||
|
||||
createModelSchema(User, { |
||||
id: primitive(), |
||||
username: primitive(), |
||||
name: primitive(), |
||||
passwordHash: primitive(), |
||||
}); |
Loading…
Reference in new issue