WIP: Accounts & Uploading
This commit is contained in:
parent
d343acc9e5
commit
2c5e40b36f
20 changed files with 388 additions and 41 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
node_modules/
|
||||
build/
|
||||
logs/
|
||||
images/
|
||||
config.json
|
|
@ -1,5 +1,6 @@
|
|||
import LoginViewModel from "../models/account/LoginViewModel";
|
||||
import RegisterViewModel from "../models/account/RegisterViewModel";
|
||||
import Config from "../objects/Config";
|
||||
import Session from "../objects/Session";
|
||||
import UserService from "../services/UserService";
|
||||
import Controller from "./Controller";
|
||||
|
@ -32,13 +33,22 @@ export default class AccountController extends Controller {
|
|||
}
|
||||
|
||||
public async Register_Post_AllowAnonymous(registerViewModel: RegisterViewModel) {
|
||||
if (typeof(registerViewModel.username) !== "string" || typeof(registerViewModel.password) !== "string") {
|
||||
if (typeof(registerViewModel.username) !== "string" || typeof(registerViewModel.password) !== "string" || typeof(registerViewModel.registerKey) !== "string" || typeof(registerViewModel.password2) !== "string" || typeof(registerViewModel.email) !== "string") {
|
||||
return this.badRequest();
|
||||
}
|
||||
|
||||
const username = registerViewModel.username.replaceAll("<", "<").replaceAll(">", ">");
|
||||
if (!await UserService.CreateUser(0, username, registerViewModel.password)) {
|
||||
if (registerViewModel.registerKey !== Config.accounts.signup.key) {
|
||||
registerViewModel.password = "";
|
||||
registerViewModel.password2 = "";
|
||||
registerViewModel.message = "Incorrect Registration Key.";
|
||||
|
||||
return this.view(registerViewModel);
|
||||
}
|
||||
|
||||
const username = registerViewModel.username.replaceAll("<", "<").replaceAll(">", ">");
|
||||
if (!await UserService.CreateUser(0, username, registerViewModel.email.trim(), registerViewModel.password)) {
|
||||
registerViewModel.password = "";
|
||||
registerViewModel.password2 = "";
|
||||
registerViewModel.message = "Sorry! That username is already taken.";
|
||||
|
||||
return this.view(registerViewModel);
|
||||
|
@ -47,6 +57,7 @@ export default class AccountController extends Controller {
|
|||
const user = await UserService.GetUserByUsername(username);
|
||||
if (!user) {
|
||||
registerViewModel.password = "";
|
||||
registerViewModel.password2 = "";
|
||||
registerViewModel.message = "Failed to create your account, please try again later.";
|
||||
|
||||
return this.view(registerViewModel);
|
||||
|
|
5
controllers/ApiController.ts
Normal file
5
controllers/ApiController.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Controller from "./Controller";
|
||||
|
||||
export default class ApiController extends Controller {
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import Session from "../objects/Session";
|
|||
import SessionUser from "../objects/SessionUser";
|
||||
import RequestCtx from "../objects/RequestCtx";
|
||||
import UserType from "../enums/UserType";
|
||||
import { cyan } from "dyetty";
|
||||
|
||||
// prepare for ts-ignore :3
|
||||
// TODO: figure out some runtime field / type checking so
|
||||
|
@ -12,6 +13,10 @@ export default abstract class Controller {
|
|||
public static FastifyInstance:FastifyInstance;
|
||||
public static RegisteredPaths:Array<string> = [];
|
||||
|
||||
private logInfo(logText: string) {
|
||||
Console.printInfo(`[ ${cyan("CONTROLLER")} ] ${logText}`);
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
|
||||
const rawControllerParts = this.constructor.name.split("_");
|
||||
|
@ -24,7 +29,7 @@ export default abstract class Controller {
|
|||
const userType = prop.split("$")[1];
|
||||
// @ts-ignore
|
||||
controllerAuthLevels.push(UserType[userType]);
|
||||
Console.printInfo(`Set Auth level requirement for ${this.constructor.name} to ${userType}`);
|
||||
this.logInfo(`Set Auth level requirement for ${this.constructor.name} to ${userType}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,21 +96,21 @@ export default abstract class Controller {
|
|||
thisMethodHttpMethod = param.toLowerCase();
|
||||
// @ts-ignore
|
||||
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName === "index" ? "" : methodName}`, requestHandler);
|
||||
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName === "index" ? "" : methodName}" as ${param}`);
|
||||
this.logInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName === "index" ? "" : methodName}" as ${param}`);
|
||||
Controller.RegisteredPaths.push(`/${controllerName}/${methodName === "index" ? "" : methodName}`);
|
||||
if (methodName === "index") {
|
||||
// @ts-ignore
|
||||
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}/${methodName}`, requestHandler);
|
||||
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName}" as ${param}`);
|
||||
this.logInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}/${methodName}" as ${param}`);
|
||||
Controller.RegisteredPaths.push(`/${controllerName}/${methodName}`);
|
||||
// @ts-ignore
|
||||
Controller.FastifyInstance[param.toLowerCase()](`/${controllerName}`, requestHandler);
|
||||
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}" as ${param}`);
|
||||
this.logInfo(`Registered ${this.constructor.name}.${method} to "/${controllerName}" as ${param}`);
|
||||
Controller.RegisteredPaths.push(`/${controllerName}`);
|
||||
} else if (controllerName === "home") {
|
||||
// @ts-ignore
|
||||
Controller.FastifyInstance[param.toLowerCase()](`/${methodName}`, requestHandler);
|
||||
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/${methodName}" as ${param}`);
|
||||
this.logInfo(`Registered ${this.constructor.name}.${method} to "/${methodName}" as ${param}`);
|
||||
Controller.RegisteredPaths.push(`/${methodName}`);
|
||||
}
|
||||
} else if (param.startsWith("Auth")) {
|
||||
|
@ -116,7 +121,7 @@ export default abstract class Controller {
|
|||
}
|
||||
// @ts-ignore
|
||||
actionAuthLevels[nameWithMethod].push(UserType[userType]);
|
||||
Console.printInfo(`Set Auth level requirement for ${this.constructor.name}.${method} to ${userType}`);
|
||||
this.logInfo(`Set Auth level requirement for ${this.constructor.name}.${method} to ${userType}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +129,7 @@ export default abstract class Controller {
|
|||
for (const httpMethod of funcMethods) {
|
||||
// @ts-ignore
|
||||
Controller.FastifyInstance[httpMethod.toLowerCase()](`/`, requestHandler);
|
||||
Console.printInfo(`Registered ${this.constructor.name}.${method} to "/" as ${httpMethod}`);
|
||||
this.logInfo(`Registered ${this.constructor.name}.${method} to "/" as ${httpMethod}`);
|
||||
Controller.RegisteredPaths.push(`/`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
import Config from "../objects/Config";
|
||||
import HashFS from "../objects/HashFS";
|
||||
import Controller from "./Controller";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
export default class HomeController extends Controller {
|
||||
public Index_Get_AllowAnonymous() {
|
||||
return this.view();
|
||||
}
|
||||
|
||||
public Upload_Post_AllowAnonymous() {
|
||||
console.log(this.req.headers.authorization);
|
||||
console.log(this.req.body);
|
||||
public async Upload_Post_AllowAnonymous() {
|
||||
const data = await this.req.file();
|
||||
if (data && data.type === "file") {
|
||||
let uploadKey: string = "";
|
||||
if ("upload-key" in this.req.headers) {
|
||||
// @ts-ignore
|
||||
uploadKey = this.req.headers["upload-key"];
|
||||
if (uploadKey !== Config.accounts.signup.key) {
|
||||
return this.unauthorised("Upload key invalid or missing.");
|
||||
}
|
||||
}
|
||||
//console.log(uploadKey);
|
||||
//console.log(data.mimetype);
|
||||
const hash = await HashFS.GetHashFSInstance("images").AddFromStream(data.file);
|
||||
}
|
||||
|
||||
return this.ok();
|
||||
return this.badRequest();
|
||||
}
|
||||
}
|
17
entities/Image.ts
Normal file
17
entities/Image.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
export default class Image {
|
||||
public Id: number = Number.MIN_VALUE;
|
||||
public UserId: number = Number.MIN_VALUE;
|
||||
public DomainId: number = Number.MIN_VALUE;
|
||||
public FileName: string = "";
|
||||
public ImageTag: string = "";
|
||||
public ImageType: string = "";
|
||||
public Hash: string = "";
|
||||
public FileSize: number = Number.MIN_VALUE;
|
||||
public CreatedByUserId: number = Number.MIN_VALUE;
|
||||
public CreatedDatetime: Date = new Date();
|
||||
public LastModifiedByUserId?: number;
|
||||
public LastModifiedDatetime?: Date;
|
||||
public DeletedByUserId?: number;
|
||||
public DeletedDatetime?: Date;
|
||||
public IsDeleted: boolean = false;
|
||||
}
|
|
@ -9,6 +9,7 @@ export default class User {
|
|||
public PasswordSalt: string = "";
|
||||
public ApiKey: string = "";
|
||||
public UploadKey: string = "";
|
||||
public Verified: boolean = false;
|
||||
public CreatedByUserId: number = Number.MIN_VALUE;
|
||||
public CreatedDatetime: Date = new Date();
|
||||
public LastModifiedByUserId?: number;
|
||||
|
|
30
index.ts
30
index.ts
|
@ -12,8 +12,10 @@ import HomeController from "./controllers/HomeController";
|
|||
import Database from "./objects/Database";
|
||||
import { join } from "path";
|
||||
import AccountController from "./controllers/AccountController";
|
||||
import { magenta, blue, cyan } from "dyetty";
|
||||
import { magenta, blue, cyan, green, red } from "dyetty";
|
||||
import ConsoleUtility from "./utilities/ConsoleUtility";
|
||||
import HashFS from "./objects/HashFS";
|
||||
import { existsSync, mkdirSync, rmSync } from "fs";
|
||||
|
||||
Console.customHeader(`EUS server started at ${new Date()}`);
|
||||
|
||||
|
@ -56,7 +58,7 @@ fastify.addHook("preValidation", (req, res, done) => {
|
|||
return done();
|
||||
} else {
|
||||
// @ts-ignore
|
||||
req.logType = magenta(" STATIC ");
|
||||
req.logType = magenta("STATIC");
|
||||
}
|
||||
|
||||
done();
|
||||
|
@ -70,20 +72,32 @@ fastify.addHook("onSend", (req, res, _payload, done) => {
|
|||
});
|
||||
|
||||
fastify.setNotFoundHandler(async (req, res) => {
|
||||
|
||||
return res.status(404).view("views/404.ejs", { session: null });
|
||||
});
|
||||
|
||||
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
||||
HashFS.STARTUP_DIR = __dirname;
|
||||
new HashFS("images");
|
||||
|
||||
Controller.FastifyInstance = fastify;
|
||||
new AccountController();
|
||||
new HomeController();
|
||||
if (Config.database.enabled) {
|
||||
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
||||
} else {
|
||||
Console.printInfo(`[ ${red("DATABASE")} ] Database is disabled.`);
|
||||
}
|
||||
|
||||
fastify.listen({ port: Config.ports.web, host: "127.0.0.1" }, (err, address) => {
|
||||
if (Config.controllers.enabled && Config.database.enabled) {
|
||||
Controller.FastifyInstance = fastify;
|
||||
new AccountController();
|
||||
new HomeController();
|
||||
} else {
|
||||
Console.printInfo(`[ ${red("CONTROLLER")} ] Controllers are disabled${Config.controllers.enabled && !Config.database.enabled ? " because the database is disabled but required by the controllers." : "."} Server will operate in static mode only.`);
|
||||
}
|
||||
|
||||
fastify.listen({ port: Config.hosts.webPort, host: Config.hosts.webHost }, (err, address) => {
|
||||
if (err) {
|
||||
Console.printError(`Error occured while spinning up fastify:\n${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Console.printInfo(`Fastify listening at ${address.replace("0.0.0.0", "localhost").replace("127.0.0.1", "localhost")}`);
|
||||
Console.printInfo(`[ ${green("MAIN")} ] Listening at ${address.replace("0.0.0.0", "localhost").replace("127.0.0.1", "localhost")}`);
|
||||
});
|
|
@ -1,5 +1,8 @@
|
|||
export default interface RegisterViewModel {
|
||||
message?: string,
|
||||
registerKey: string
|
||||
username: string,
|
||||
password: string
|
||||
email: string,
|
||||
password: string,
|
||||
password2: string
|
||||
}
|
|
@ -2,18 +2,21 @@ import { readFileSync } from "fs";
|
|||
const config = JSON.parse(readFileSync("./config.json").toString());
|
||||
|
||||
export default abstract class Config {
|
||||
public static ports:IPorts = config.ports;
|
||||
public static database:IDatabase = config.database;
|
||||
public static session:ISession = config.session;
|
||||
public static controllers:IControllers = config.controllers;
|
||||
public static accounts:IAccounts = config.accounts;
|
||||
public static instance: string = config.instance;
|
||||
public static hosts: IHosts = config.hosts;
|
||||
public static database: IDatabase = config.database;
|
||||
public static session: ISession = config.session;
|
||||
public static controllers: IControllers = config.controllers;
|
||||
public static accounts: IAccounts = config.accounts;
|
||||
}
|
||||
|
||||
interface IPorts {
|
||||
web: number
|
||||
interface IHosts {
|
||||
webHost: string,
|
||||
webPort: number
|
||||
}
|
||||
|
||||
interface IDatabase {
|
||||
enabled: boolean,
|
||||
address: string,
|
||||
port: number,
|
||||
username: string,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { blue } from "dyetty";
|
||||
import { Console } from "hsconsole";
|
||||
import { createPool, Pool, RowDataPacket } from "mysql2";
|
||||
|
||||
|
@ -21,7 +22,7 @@ export default class Database {
|
|||
database: databaseName
|
||||
});
|
||||
|
||||
Console.printInfo(`DB connection pool created. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||
Console.printInfo(`[ ${blue("DATABASE")} ] DB connection pool created. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||
|
||||
Database.Instance = this;
|
||||
}
|
||||
|
|
140
objects/HashFS.ts
Normal file
140
objects/HashFS.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
// ! Hashed File Store (not file system!!)
|
||||
|
||||
import { join } from "path";
|
||||
import { existsSync, mkdirSync, createWriteStream, rename, stat, writeFile, copyFile, rm, rmSync, } from "fs";
|
||||
import { Console } from "hsconsole";
|
||||
import { yellow } from "dyetty";
|
||||
import { createHash, randomBytes } from "crypto";
|
||||
import FunkyArray from "funky-array";
|
||||
import { Stream } from "stream";
|
||||
|
||||
export default class HashFS {
|
||||
public static STARTUP_DIR: string;
|
||||
private static HASHFS_INSTANCES: FunkyArray<string, HashFS> = new FunkyArray<string, HashFS>();
|
||||
|
||||
public static GetHashFSInstance(name: string) {
|
||||
const instance = this.HASHFS_INSTANCES.get(name);
|
||||
if (!instance) {
|
||||
throw `Attempted to get nonexistent HashFS instance "${name}"`;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private readonly path: string;
|
||||
private readonly tempPath: string;
|
||||
private readonly folder: string;
|
||||
|
||||
private logInfo(logText: string) {
|
||||
Console.printInfo(`[ ${yellow(`HashFS: ${this.folder}`)} ] ${logText}`);
|
||||
}
|
||||
|
||||
public constructor(folder: string) {
|
||||
HashFS.HASHFS_INSTANCES.set(folder, this);
|
||||
|
||||
this.folder = folder;
|
||||
this.path = join(HashFS.STARTUP_DIR, folder);
|
||||
|
||||
let firstCreation = false;
|
||||
if (!existsSync(this.path)) {
|
||||
this.logInfo(`Creating HashFS for "${folder}"...`);
|
||||
mkdirSync(this.path);
|
||||
firstCreation = true;
|
||||
}
|
||||
|
||||
this.logInfo(`Validating file store...`);
|
||||
let issuesRepaired = 0;
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const hashRootFolderPath = join(this.path, i.toString(16));
|
||||
if (!existsSync(hashRootFolderPath)) {
|
||||
mkdirSync(hashRootFolderPath);
|
||||
this.logInfo(`"${i.toString(16)}" does not exist, creating...`);
|
||||
issuesRepaired++;
|
||||
}
|
||||
for (let i1 = 0; i1 < 16; i1++) {
|
||||
const subFolderPath = join(hashRootFolderPath, (i * 16 + i1).toString(16).padStart(2, "0"));
|
||||
if (!existsSync(subFolderPath)) {
|
||||
this.logInfo(`"${i.toString(16)}/${(i * 16 + i1).toString(16).padStart(2, "0")}" does not exist, creating...`);
|
||||
mkdirSync(subFolderPath);
|
||||
issuesRepaired++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Validate the files in the file store
|
||||
this.logInfo(`File Store Validated Successfully${!firstCreation && issuesRepaired > 0 ? `. Repaired ${issuesRepaired} issues.` : " with no issues."}`);
|
||||
|
||||
this.tempPath = join(this.path, "temp");
|
||||
if (existsSync(this.tempPath)) {
|
||||
rmSync(this.tempPath, { recursive: true });
|
||||
}
|
||||
mkdirSync(this.tempPath);
|
||||
this.logInfo(`Created temp working folder at "${this.tempPath}"`);
|
||||
|
||||
this.logInfo(`Ready!`);
|
||||
}
|
||||
|
||||
private getFilePath(hash: string) {
|
||||
return join(this.path, hash[0], `${hash[0]}${hash[1]}`, hash);
|
||||
}
|
||||
|
||||
public AddFile(contents: Buffer | string) {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const hasher = createHash("sha1");
|
||||
hasher.setEncoding("hex");
|
||||
hasher.write(contents);
|
||||
hasher.end();
|
||||
const hash: string = hasher.read();
|
||||
|
||||
if (await this.FileExists(hash)) {
|
||||
return resolve(hash);
|
||||
}
|
||||
|
||||
writeFile(this.getFilePath(hash), contents, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(hash);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public AddFromStream(stream: Stream) {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const hasher = createHash("sha1");
|
||||
hasher.setEncoding("hex");
|
||||
|
||||
const tempFilePath = join(this.tempPath, randomBytes(16).toString("base64url"));
|
||||
const tempFile = createWriteStream(tempFilePath);
|
||||
stream.pipe(tempFile);
|
||||
stream.pipe(hasher);
|
||||
tempFile.on("close", () => {
|
||||
const hash: string = hasher.read();
|
||||
rename(tempFilePath, this.getFilePath(hash), (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
this.logInfo(`Stored file as ${hash}`);
|
||||
resolve(hash);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public FileExists(hash: string) {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
stat(this.getFilePath(hash), (err, _stat) => {
|
||||
if (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
resolve(false);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ export default abstract class Session {
|
|||
validPeriod.setTime(validPeriod.getTime() + Config.session.validity);
|
||||
const key = randomBytes(Config.session.length).toString("hex");
|
||||
|
||||
Session.Sessions.set(key, new SessionUser(user.Id, user.UserType, validPeriod));
|
||||
Session.Sessions.set(key, new SessionUser(user.Id, user.Username, user.UserType, validPeriod));
|
||||
|
||||
res.setCookie("EHP_SESSION", key, {
|
||||
path: "/",
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import UserType from "../enums/UserType";
|
||||
|
||||
export default class SessionUser {
|
||||
public readonly userId:number;
|
||||
public readonly userType:UserType;
|
||||
public readonly validityPeriod:Date;
|
||||
public readonly userId: number;
|
||||
public readonly username: string;
|
||||
public readonly userType: UserType;
|
||||
public readonly validityPeriod: Date;
|
||||
|
||||
constructor(userId:number, userType: UserType, validityPeriod:Date) {
|
||||
constructor(userId:number, username: string, userType: UserType, validityPeriod:Date) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.userType = userType;
|
||||
this.validityPeriod = validityPeriod;
|
||||
}
|
||||
|
|
61
repos/ImageRepo.ts
Normal file
61
repos/ImageRepo.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import Image from "../entities/Image";
|
||||
import Database from "../objects/Database";
|
||||
import RepoBase from "./RepoBase";
|
||||
|
||||
export default abstract class ImageRepo {
|
||||
public static async SelectAll() {
|
||||
const dbImage = await Database.Instance.query("SELECT * FROM Image WHERE IsDeleted = 0");
|
||||
const images = new Array<Image>();
|
||||
|
||||
for (const row of dbImage) {
|
||||
const image = new Image();
|
||||
PopulateImageFromDB(image, row);
|
||||
images.push(image);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
public static async SelectById(id:number) {
|
||||
const dbImage = await Database.Instance.query("SELECT * FROM Image WHERE Id = ? LIMIT 1", [id]);
|
||||
if (dbImage == null || dbImage.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
const image = new Image();
|
||||
PopulateImageFromDB(image, dbImage[0]);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public static async InsertUpdate(image: Image) {
|
||||
if (image.Id === Number.MIN_VALUE) {
|
||||
image.Id = (await Database.Instance.query("INSERT Image (UserId, DomainId, FileName, ImageTag, ImageType, Hash, FileSize, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
image.UserId, image.DomainId, image.FileName, image.ImageTag, image.Hash, image.FileSize, image.CreatedByUserId, image.CreatedDatetime.getTime(), image.LastModifiedByUserId ?? null, image.LastModifiedDatetime?.getTime() ?? null, image.DeletedByUserId ?? null, image.DeletedDatetime?.getTime() ?? null, Number(image.IsDeleted)
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE Image SET UserId = ?, DomainId = ?, FileName = ?, ImageTag = ?, ImageType = ?, Hash = ?, FileSize = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
image.UserId, image.DomainId, image.FileName, image.ImageTag, image.Hash, image.FileSize, image.CreatedByUserId, image.CreatedDatetime.getTime(), image.LastModifiedByUserId ?? null, image.LastModifiedDatetime?.getTime() ?? null, image.DeletedByUserId ?? null, image.DeletedDatetime?.getTime() ?? null, Number(image.IsDeleted), image.Id
|
||||
]);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
function PopulateImageFromDB(image: Image, dbImage: any) {
|
||||
image.Id = dbImage.Id;
|
||||
image.UserId = dbImage.UserId;
|
||||
image.DomainId = dbImage.DomainId;
|
||||
image.FileName = dbImage.FileName;
|
||||
image.ImageTag = dbImage.ImageTag;
|
||||
image.ImageType = dbImage.ImageType;
|
||||
image.Hash = dbImage.Hash;
|
||||
image.FileSize = dbImage.FileSize;
|
||||
image.CreatedByUserId = dbImage.CreatedByUserId;
|
||||
image.CreatedDatetime = new Date(dbImage.CreatedDatetime);
|
||||
image.LastModifiedByUserId = dbImage.LastModifiedByUserId;
|
||||
image.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbImage.LastModifiedDatetime);
|
||||
image.DeletedByUserId = dbImage.DeletedByUserId;
|
||||
image.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbImage.DeletedDatetime);
|
||||
image.IsDeleted = dbImage.IsDeleted[0] === 1;
|
||||
}
|
|
@ -51,12 +51,12 @@ export default abstract class UserRepo {
|
|||
|
||||
public static async InsertUpdate(user:User) {
|
||||
if (user.Id === Number.MIN_VALUE) {
|
||||
user.Id = (await Database.Instance.query("INSERT User (UserTypeId, Username, PasswordHash, PasswordSalt, ApiKey, UploadKey, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
user.UserType, user.Username, user.PasswordHash, user.PasswordSalt, user.ApiKey, user.UploadKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||
user.Id = (await Database.Instance.query("INSERT User (UserTypeId, Username, EmailAddress, PasswordHash, PasswordSalt, ApiKey, UploadKey, Verified, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||
user.UserType, user.Username, user.EmailAddress, user.PasswordHash, user.PasswordSalt, user.ApiKey, user.UploadKey, Number(user.Verified), user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
await Database.Instance.query(`UPDATE User SET UserTypeId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
user.UserType, user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
await Database.Instance.query(`UPDATE User SET UserTypeId = ?, Username = ?, EmailAddress = ?, PasswordHash = ?, PasswordSalt = ?, ApiKey = ?, UploadKey = ?, Verified = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||
user.UserType, user.Username, user.EmailAddress, user.PasswordHash, user.PasswordSalt, user.ApiKey, user.UploadKey, Number(user.Verified), user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -68,8 +68,12 @@ function PopulateUserFromDB(user:User, dbUser:any) {
|
|||
user.Id = dbUser.Id;
|
||||
user.UserType = dbUser.UserTypeId;
|
||||
user.Username = dbUser.Username;
|
||||
user.EmailAddress = dbUser.EmailAddress;
|
||||
user.PasswordHash = dbUser.PasswordHash;
|
||||
user.PasswordSalt = dbUser.PasswordSalt;
|
||||
user.ApiKey = dbUser.ApiKey;
|
||||
user.UploadKey = dbUser.UploadKey;
|
||||
user.Verified = dbUser.Verified[0] === 1;
|
||||
user.CreatedByUserId = dbUser.CreatedByUserId;
|
||||
user.CreatedDatetime = new Date(dbUser.CreatedDatetime);
|
||||
user.LastModifiedByUserId = dbUser.LastModifiedByUserId;
|
||||
|
|
|
@ -50,7 +50,7 @@ export default abstract class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
public static async CreateUser(currentUserId:number, username:string, password:string) {
|
||||
public static async CreateUser(currentUserId: number, username: string, email: string, password: string) {
|
||||
try {
|
||||
const existingCheck = await UserRepo.SelectByUsername(username);
|
||||
if (existingCheck) {
|
||||
|
@ -60,6 +60,7 @@ export default abstract class UserService {
|
|||
const user = new User();
|
||||
user.UserType = UserType.User;
|
||||
user.Username = username;
|
||||
user.EmailAddress = email;
|
||||
user.PasswordSalt = PasswordUtility.GenerateSalt();
|
||||
user.PasswordHash = await PasswordUtility.HashPassword(user.PasswordSalt, password);
|
||||
user.CreatedByUserId = currentUserId;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { green, yellow, red, gray } from "dyetty";
|
||||
import { Console } from "hsconsole";
|
||||
|
||||
export default abstract class ConsoleUtility {
|
||||
public static StatusColor(statusCode: number) {
|
||||
|
|
27
views/account/login.ejs
Normal file
27
views/account/login.ejs
Normal file
|
@ -0,0 +1,27 @@
|
|||
<%- include("../base/header", { title: "Login", session }) %>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="card my-auto" style="width: 25rem;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">EUS Login</h5>
|
||||
<% if (typeof(message) === "string") { %>
|
||||
<div class="alert alert-danger text-center" role="alert"><%= message %></div>
|
||||
<% } %>
|
||||
<form action="/account/login" method="POST">
|
||||
<input type="hidden" name="returnTo" value="<%= typeof(returnTo) === "undefined" ? "" : returnTo %>" >
|
||||
<input class="form-control mt-3 mb-2" name="username" placeholder="Username" value="<%= typeof(username) === "undefined" ? "" : username %>" required />
|
||||
<input class="form-control mb-3" name="password" type="password" placeholder="Password" required />
|
||||
<div class="row">
|
||||
<div class="col d-flex justify-content-center">
|
||||
<a class="align-self-center" href="/account/register">I don't have an account.</a>
|
||||
</div>
|
||||
<div class="col-auto me-3">
|
||||
<input class="btn btn-primary mx-auto d-block" type="submit" value="Login" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include("../base/footer") %>
|
35
views/account/register.ejs
Normal file
35
views/account/register.ejs
Normal file
|
@ -0,0 +1,35 @@
|
|||
<%- include("../base/header", { title: "Register", session }) %>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="card my-auto" style="width: 25rem;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">EUS Registration</h5>
|
||||
<% if (typeof(message) === "string") { %>
|
||||
<div class="alert alert-danger text-center" role="alert"><%= message %></div>
|
||||
<% } %>
|
||||
<form action="/account/register" method="POST">
|
||||
<input type="hidden" name="returnTo" value="<%= typeof(returnTo) === "undefined" ? "" : returnTo %>" >
|
||||
<div class="input-group mt-3 mb-2">
|
||||
<span class="input-group-text"><i class="bi bi-key-fill"></i></span>
|
||||
<input class="form-control" name="registerKey" placeholder="Registration Key" value="<%= typeof(registerKey) === "undefined" ? "" : registerKey %>" required autocomplete="new-password" />
|
||||
</div>
|
||||
<hr>
|
||||
<input class="form-control mt-3 mb-2" name="username" placeholder="Username" value="<%= typeof(username) === "undefined" ? "" : username %>" required autocomplete="new-password" />
|
||||
<input class="form-control mt-3 mb-2" name="email" type="email" placeholder="Email Address" value="<%= typeof(email) === "undefined" ? "" : email %>" required autocomplete="new-password" />
|
||||
<hr>
|
||||
<input class="form-control mb-3" name="password" type="password" placeholder="Password" required autocomplete="new-password" />
|
||||
<input class="form-control mb-3" name="password2" type="password" placeholder="Confirm Password" required autocomplete="new-password" />
|
||||
<div class="row">
|
||||
<div class="col d-flex justify-content-center">
|
||||
<a class="align-self-center" href="/account/login">I have an account!</a>
|
||||
</div>
|
||||
<div class="col-auto me-3">
|
||||
<input class="btn btn-primary mx-auto d-block" type="submit" value="Register" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include("../base/footer") %>
|
Loading…
Reference in a new issue