import * as bcrypt from "bcrypt"; import * as r from "rethinkdb"; import { createModelSchema, primitive, serialize, update } from "serializr"; import { Database } from "./Database"; 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 table() { return this.db.db.table(User.tableName); } constructor(db: Database, data?: Partial) { 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 { 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 load(db: Database, id: string): Promise { const data = await db.db.table(User.tableName) .get(id) .run(db.conn); if (data == null) { return null; } return new User(db, data); } static async loadByUsername(db: Database, username: string): Promise { 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; await 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, user.nth(0).update(data) as r.Expression) .run(this.db.conn); return a.inserted > 0; } async setPassword(newPassword: string): Promise { this.passwordHash = await bcrypt.hash(newPassword, HASH_ROUNDS); } async comparePassword(password: string): Promise { 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(), });