This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
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.
 
 
 
 
bandolier-template/lib/models.js

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') {
}