You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
5.4 KiB
185 lines
5.4 KiB
import { knex, Model } from './ormish.js';
|
|
import bcrypt from 'bcryptjs';
|
|
import {v4 as uuid} from "uuid";
|
|
import logging from "./logging.js";
|
|
import assert from "assert";
|
|
import crypto from "crypto";
|
|
|
|
const log = logging.create("lib/models.js");
|
|
|
|
export const RESET_CODE_SIZE = 8; // how big to make the unique email reset code
|
|
export const UNSUB_CODE_SIZE = 16; // how big to make the unique email unsubscribe code
|
|
|
|
export class User extends Model.from_table('user') {
|
|
|
|
static async auth(username, password) {
|
|
try {
|
|
const uname = username.toLowerCase();
|
|
const res = await knex('user').where({email: uname}).select();
|
|
|
|
if(res === undefined) {
|
|
log.error(res, "Received undefined but knex claims only Array returns.");
|
|
return undefined;
|
|
} else if(res.length > 1) {
|
|
log.error(res, `Multiple accounts under the same email ${uname}`);
|
|
return undefined;
|
|
} else if(res.length === 0) {
|
|
// no user found
|
|
return undefined;
|
|
} else {
|
|
// user found, check password
|
|
const user = res[0];
|
|
const good = bcrypt.compareSync(password, user.password);
|
|
return good ? user : undefined;
|
|
}
|
|
} catch(error) {
|
|
log.error(error);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs all the cleanup and checks needed for a registration. It will
|
|
* ensure that the password and password_repeat are the same, lowercase the email,
|
|
* clean out unwanted attrbiutes with User.clean, generate required keys, etc.
|
|
*
|
|
* @param {Object} attr - attributes usually from a web form
|
|
* @return {Object} undefined or the new user on success
|
|
*/
|
|
static async register(attr) {
|
|
let user = undefined;
|
|
|
|
if(attr.password != attr.password_repeat) {
|
|
return undefined;
|
|
} else {
|
|
user = User.clean(attr);
|
|
}
|
|
|
|
// TODO: force emails to lowercase here rather than in the database?
|
|
user.email = user.email.toLowerCase();
|
|
|
|
// validate here?
|
|
let exists = await User.first({email: user.email});
|
|
|
|
if(exists) {
|
|
log.error("User exists", user.email);
|
|
return undefined;
|
|
} else {
|
|
// BUG: accepting unfiltered user input
|
|
user.password = User.encrypt_password(user.password);
|
|
user.unsubkey = User.random_hex(UNSUB_CODE_SIZE);
|
|
|
|
let res = await knex('user').insert(user);
|
|
user.id = res[0];
|
|
|
|
return res.length == 0 ? undefined : user;
|
|
}
|
|
}
|
|
|
|
static random_hex(size) {
|
|
assert(size, `random_hex size can't be falsy ${ size }`);
|
|
return crypto.randomBytes(size).toString("hex");
|
|
}
|
|
|
|
static encrypt_password(password) {
|
|
assert(password, `Password cannot be falsy: ${ password }`);
|
|
let salt = bcrypt.genSaltSync(10);
|
|
return bcrypt.hashSync(password, salt);
|
|
}
|
|
|
|
async emails(setting) {
|
|
// don't change it if it's already set this way
|
|
if(this.unsubscribe !== setting) {
|
|
return await User.update({id: this.id}, {
|
|
unsubscribe: setting, unsubscribed_on: Date.now()
|
|
});
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Users have many Payments, but Payment has only one User. */
|
|
async payments() {
|
|
return await this.has_many(Payment, { user_id: this.id });
|
|
}
|
|
}
|
|
|
|
|
|
export class Payment extends Model.from_table('payment') {
|
|
/* Payment has only one User, but User has many Payments. */
|
|
get user() {
|
|
return this.user_id !== undefined ? this.has_one(User, { id: this.user_id }) : undefined;
|
|
}
|
|
|
|
static fake_payment() {
|
|
return Payment.insert({
|
|
system: 'fake',
|
|
status: 'complete',
|
|
internal_id: Payment.gen_internal_id(),
|
|
sys_primary_id: Payment.gen_internal_id(),
|
|
sys_secondary_id: Payment.gen_internal_id(),
|
|
sys_created_on: new Date()
|
|
});
|
|
}
|
|
|
|
|
|
static gen_internal_id() {
|
|
return uuid();
|
|
}
|
|
|
|
static async paid(user) {
|
|
assert(user !== undefined, "Invalid user given to Payment.paid");
|
|
assert(user.id !== undefined, "User object given has an user.id === undefined.");
|
|
|
|
// ignore refunded payments
|
|
const payment = await Payment.first(builder => {
|
|
builder
|
|
.whereNotIn("status", ["refunded", "failed", "pending"])
|
|
.where({user_id: user.id})
|
|
}, ["user_id", "system", "status", "status_reason"]);
|
|
|
|
// it's paid if there's a payment and it is complete
|
|
const paid = payment !== undefined && payment.status === "complete";
|
|
|
|
// this now returns paid and also paid is false with more info about why
|
|
return [paid, payment];
|
|
}
|
|
}
|
|
|
|
export class Media extends Model.from_table("media") {
|
|
}
|
|
|
|
|
|
export class Site extends Model.from_table("site") {
|
|
static async get(key) {
|
|
const row = await knex(this.table_name).first().where({key});
|
|
return row !== undefined ? JSON.parse(row.value) : undefined;
|
|
}
|
|
|
|
static async set(key, value) {
|
|
const json_value = JSON.stringify(value);
|
|
return await Site.upsert({key, value: json_value}, "key");
|
|
}
|
|
|
|
// TODO: figure out how to get sqlite3 to do a return of the value
|
|
static async increment(key, count) {
|
|
return await knex(this.table_name).where({key}).increment("value", count);
|
|
}
|
|
|
|
static async decrement(key, count) {
|
|
return await knex(this.table_name).where({key}).decrement("value", count);
|
|
}
|
|
}
|
|
|
|
export class Livestream extends Model.from_table("livestream") {
|
|
media() {
|
|
return Media.first({id: this.media_id});
|
|
}
|
|
|
|
static add_viewers(id) {
|
|
return knex(this.table_name).where({id}).increment('viewer_count', 1);
|
|
}
|
|
}
|
|
|
|
export class Product extends Model.from_table('product') {
|
|
}
|
|
|