diff --git a/_test/OrderedObject.test.js b/_test/OrderedObject.test.js
new file mode 100644
index 0000000..c4e6033
--- /dev/null
+++ b/_test/OrderedObject.test.js
@@ -0,0 +1,27 @@
+import OrderedObject from "../server/db/model/OrderedObject.js"
+
+window.testSuites.push(
+
+ class testOrderedObject {
+
+ async addShouldFailIfKeyIsDuplicate() {
+ class Test extends OrderedObject {
+
+ }
+
+ let test = new Test()
+
+ test.add("1", {name: "hello"})
+
+ try {
+ test.add("1", {name: "bye"})
+ } catch(e){
+ return
+ }
+
+ return "Received no error!"
+ }
+
+ }
+
+)
\ No newline at end of file
diff --git a/_test/test.js b/_test/test.js
new file mode 100644
index 0000000..9f7f5cf
--- /dev/null
+++ b/_test/test.js
@@ -0,0 +1,57 @@
+let scriptToPaste = `
+
+`;
+console.log("Tests initializing.")
+window.testSuites = [];
+
+/* Server - DB */
+import ("./OrderedObject.test.js")
+
+window.test = async function() {
+ // window.testSuites.sort();
+ window.alert = () => true;
+ window.confirm = () => true;
+
+ console.clear();
+
+ let failed = 0;
+ let success = 0;
+
+ var start = new Date();
+ for(let j=0; j new Promise(res => setTimeout(res, ms));
+
+window.__defineGetter__("test", test);
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..e0b254b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Node Terminal
+
+
+
+
+
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..ceadd76
--- /dev/null
+++ b/main.js
@@ -0,0 +1,30 @@
+// main.js
+const { app, BrowserWindow, nativeImage } = require('electron');
+const path = require('path');
+
+if (process.platform === 'darwin') {
+ setTimeout(() => {
+ app.dock.bounce('informational'); // or 'critical'
+ }, 1000);
+}
+
+function createWindow() {
+ const win = new BrowserWindow({
+ show: false, // window is hidden
+ width: 1200,
+ height: 800,
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ icon: path.join(__dirname, '_', 'fabric.png')
+ }
+ });
+ win.loadFile(path.join(__dirname, 'index.html'));
+ win.webContents.openDevTools({ mode: 'undocked' });
+}
+
+app.whenReady().then(createWindow);
+
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') app.quit();
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index 2292054..a1c51f2 100644
--- a/package.json
+++ b/package.json
@@ -1,25 +1,22 @@
{
"name": "Hyperia",
- "type": "module",
"version": "1.0.0",
- "description": "",
- "main": "index.js",
+ "main": "main.js",
"scripts": {
- "start": "node server/index.js",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "start": "electron ."
},
- "author": "",
- "license": "ISC",
"dependencies": {
"argon2": "^0.44.0",
- "chalk": "^5.6.2",
+ "chalk": "^4.1.2",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^4.18.2",
- "http-proxy": "^1.18.1",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"ws": "^8.18.3"
+ },
+ "devDependencies": {
+ "electron": "^25.0.0"
}
-}
+}
\ No newline at end of file
diff --git a/server/_/quilldb.js b/server/_/quilldb.js
index 09df8ee..51aa1d3 100644
--- a/server/_/quilldb.js
+++ b/server/_/quilldb.js
@@ -1,7 +1,7 @@
-import chalk from 'chalk'
-import path from 'path';
-import fs from 'fs/promises';
-import { pathToFileURL } from 'url';
+const chalk = require('chalk');
+const path = require('path');
+const fs = require('fs/promises');
+const { pathToFileURL } = require('url');
function Node(node) {
let traits = [
diff --git a/server/auth.js b/server/auth.js
index 0a53bb5..de4ce97 100644
--- a/server/auth.js
+++ b/server/auth.js
@@ -1,8 +1,7 @@
-import dotenv from 'dotenv';
-import chalk from 'chalk';
-import jwt from 'jsonwebtoken'
-import argon2 from 'argon2'
-import { randomUUID } from 'node:crypto'
+const dotenv = require("dotenv")
+const jwt = require('jsonwebtoken');
+const argon2 = require('argon2');
+
dotenv.config();
export default class AuthHandler {
diff --git a/server/db/db.js b/server/db/db.js
index b04c2e5..c78ab58 100644
--- a/server/db/db.js
+++ b/server/db/db.js
@@ -1,29 +1,21 @@
+const fs = require('fs/promises');
+const chalk = require('chalk');
+const path = require('path');
import QuillDB from "../_/quilldb.js"
-import fs from 'fs/promises'
-import path from 'path'
-import Title from "./model/Title.js"
-import Member from './model/Member.js'
-import Token from './model/Token.js'
+import Titles from "./model/Titles.js"
+import Members from './model/Members.js'
+import Tokens from './model/Tokens.js'
export default class Database {
- nodes = [];
- types = [
- {
- validate: Title,
- start: 0,
- end: null,
- },
- {
- validate: Member,
- start: null,
- end: null,
- },
- {
- validate: Token,
- start: null,
- end: null,
- },
- ]
+ titles = new Titles()
+ members = new Members()
+ tokens = new Tokens()
+
+ fromID = {
+ "HY": this.titles,
+ "MEMBER": this.members,
+ "TOKEN": this.tokens
+ }
constructor() {
this.loadData()
@@ -38,82 +30,25 @@ export default class Database {
dbJson = []
}
let nodes = dbJson["nodes"];
- this.validateNodes(nodes)
- }
-
- validateNodes(nodes) {
- nodes = Object.entries(nodes)
-
- let t = 0
-
- let currentType = () => {return this.types[t]}
- let nextType = () => {return this.types[t+1]}
- let selectNextType = () => {
- currentType().end = t
- t += 1;
- currentType().start = t
- }
- let lastNode = (i=null) => {
- if(i == null) throw new Error("must pass a param to lastNode()")
- return i+1 === nodes.length
- }
-
- for(let i=0; i {
- return this.nodes[id]
- },
- userByEmail: (email) => {
- for (const id of this.labels["User"]) {
- const user = this.get.user(id);
- if (user.email === email) {
- return { id, ...user }
- }
- }
- return null;
- },
- token: (id) => {
- return this.nodes[`TOKEN-${id}`]
- }
- }
-
- add = {
- user: (node) => {
- let lastUser = {}
-
- }
- }
-
- submitNewUser(qrCodeID, userInfo) {
- let newUser = {
- labels: ["User"],
- ...userInfo
- }
- if(User(newUser))
- this.add.user(newUser)
- }
-
generateUserID() {
let id = this.labels["User"].length + 1;
while (this.get.user(`user-${id}`)) {
diff --git a/server/db/model/Member.js b/server/db/model/Member.js
deleted file mode 100644
index 4fd501e..0000000
--- a/server/db/model/Member.js
+++ /dev/null
@@ -1,43 +0,0 @@
-export default function Member(id, node) {
- let idTraits = {
- firstWord: "MEMBER",
- length: 2
- }
-
- let fields = [
- "firstName",
- "lastName",
- "email",
- "password"
- ]
-
- let checkID = () => {
- let split = id.split("-")
- return (
- split.length === idTraits.length
- && split[0] === idTraits.firstWord
- && !isNaN(Number(split[1]))
- )
- }
- let idres = checkID()
- if(!idres) {
- return false
- }
-
- let checkFields = () => {
- for(let i = 0; i < fields.length; i++) {
- if(!node[fields[i]]) {
- throw new Error(`Member ${node.email} is missing trait ${fields[i]}`)
- return false
- } else {
- return true
- }
- }
- }
- let fieldres = checkFields()
- if(!fieldres) {
- return false
- }
-
- return true
-}
\ No newline at end of file
diff --git a/server/db/model/Members.js b/server/db/model/Members.js
new file mode 100644
index 0000000..42271bc
--- /dev/null
+++ b/server/db/model/Members.js
@@ -0,0 +1,64 @@
+import OrderedObject from "./OrderedObject.js"
+
+export default class Members extends OrderedObject {
+
+ add(newMember) {
+ console.log("adding ", newMember)
+ let id = `MEMBER-${newMember.email}`
+ if(this.validate(id, newMember)) {
+ try {
+ super.add(id, newMember)
+ } catch(e) {
+ console.error(e)
+ throw e
+ }
+ } else {
+ throw new global.ServerError(400, "Invalid Member Data!");
+ }
+ }
+
+ validate(id, node) {
+ let idTraits = {
+ firstWord: "MEMBER"
+ }
+
+ let fields = [
+ "firstName",
+ "lastName",
+ "email",
+ "password"
+ ]
+
+ let checkID = () => {
+ let split = id.split("-")
+ return (
+ split[0] === idTraits.firstWord
+ && split[1].includes("@")
+ && split[1].includes(".")
+ )
+ }
+ let idres = checkID()
+ if(!idres) {
+ console.log("id failed: ", id)
+ return false
+ }
+
+ let checkFields = () => {
+ for(let i = 0; i < fields.length; i++) {
+ if(!node[fields[i]]) {
+ throw new Error(`Member ${node.email} is missing trait ${fields[i]}`)
+ return false
+ } else {
+ return true
+ }
+ }
+ }
+ let fieldres = checkFields()
+ if(!fieldres) {
+ console.log("fields failed")
+ return false
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/server/db/model/OrderedObject.js b/server/db/model/OrderedObject.js
new file mode 100644
index 0000000..5d91869
--- /dev/null
+++ b/server/db/model/OrderedObject.js
@@ -0,0 +1,29 @@
+export default class OrderedObject {
+ entries = []
+ ids = {}
+
+ add(id, data) {
+ if(this.get(id)) {
+ console.error(`Can't add item ${id}: id already exists`)
+ throw new global.ServerError(400, `Member with this email already exists`)
+ }
+ this.entries.push(data)
+ this.ids[id] = this.entries.length - 1
+ }
+
+ delete(key) {
+ if (typeof key === "number") {
+ return this.entries[key]
+ } else {
+ return this.entries[this.ids[key]]
+ }
+ }
+
+ get(key) {
+ if (typeof key === "number") {
+ return this.entries[key]
+ } else {
+ return this.entries[this.ids[key]]
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/db/model/Title.js b/server/db/model/Title.js
deleted file mode 100644
index 1a5ffdb..0000000
--- a/server/db/model/Title.js
+++ /dev/null
@@ -1,33 +0,0 @@
-export default function Title(id, node) {
- let checkID = () => {
- let split = id.split("-")
- return (
- split.length === 2
- && split[0] === "HY"
- && !isNaN(Number(split[1]))
- )
- }
- let idres = checkID()
- if(!idres) {
- return false
- }
-
- let checkFields = () => {
- let fields = [
- "fullName",
- ]
- for(let i = 0; i < fields.length; i++) {
- if(!node[fields[i]]) {
- throw new Error(`Title ${id} is missing trait ${fields[i]}`)
- return false
- }
- }
- return true
- }
- let fieldres = checkFields()
- if(!fieldres) {
- return false
- }
-
- return true
-}
\ No newline at end of file
diff --git a/server/db/model/Titles.js b/server/db/model/Titles.js
new file mode 100644
index 0000000..5e37cfc
--- /dev/null
+++ b/server/db/model/Titles.js
@@ -0,0 +1,53 @@
+import OrderedObject from "./OrderedObject.js"
+
+export default class Titles extends OrderedObject {
+
+ add(newTitle) {
+ let id = `HY-${this.entries.length+1}`
+ console.log(id)
+ if(this.validate(id, newTitle)) {
+ try {
+ super.add(id, newTitle)
+ } catch(e) {
+ console.error(e)
+ throw e
+ }
+ } else {
+ throw new global.ServerError(400, "Invalid Member Data!");
+ }
+ }
+
+ validate(id, node) {
+ let checkID = () => {
+ let split = id.split("-")
+ return (
+ split.length === 2
+ && split[0] === "HY"
+ && !isNaN(Number(split[1]))
+ )
+ }
+ let idres = checkID()
+ if(!idres) {
+ return false
+ }
+
+ let checkFields = () => {
+ let fields = [
+ "fullName",
+ ]
+ for(let i = 0; i < fields.length; i++) {
+ if(!node[fields[i]]) {
+ throw new Error(`Title ${id} is missing trait ${fields[i]}`)
+ return false
+ }
+ }
+ return true
+ }
+ let fieldres = checkFields()
+ if(!fieldres) {
+ return false
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/server/db/model/Token.js b/server/db/model/Token.js
deleted file mode 100644
index 06f0c12..0000000
--- a/server/db/model/Token.js
+++ /dev/null
@@ -1,39 +0,0 @@
-export default function Token(id, node) {
- let idTraits = {
- firstWord: "TOKEN"
- }
-
- let fields = [
- "index",
- "url",
- "used"
- ]
-
- let checkID = () => {
- let split = id.split("-")
- return (
- split[0] === idTraits.firstWord
- )
- }
- let idres = checkID()
- if(!idres) {
- return false
- }
-
- let checkFields = () => {
- for(let i = 0; i < fields.length; i++) {
- if(!node[fields[i]]) {
- throw new Error(`Token ${node.email} is missing trait ${fields[i]}`)
- return false
- } else {
- return true
- }
- }
- }
- let fieldres = checkFields()
- if(!fieldres) {
- return false
- }
-
- return true
-}
\ No newline at end of file
diff --git a/server/db/model/Tokens.js b/server/db/model/Tokens.js
new file mode 100644
index 0000000..8d74b5d
--- /dev/null
+++ b/server/db/model/Tokens.js
@@ -0,0 +1,62 @@
+import OrderedObject from "./OrderedObject.js"
+
+export default class Tokens extends OrderedObject {
+
+ add(token) {
+ let id = `TOKEN-${token.uuid}`
+ if(this.validate(id, token)) {
+ try {
+ super.add(id, token)
+ } catch(e) {
+ console.error(e)
+ throw e
+ }
+ } else {
+ throw new global.ServerError(400, "Invalid Member Data!");
+ }
+ }
+
+ get(uuid) {
+ return super.get(`TOKEN-${uuid}`)
+ }
+
+ validate(id, node) {
+ let idTraits = {
+ firstWord: "TOKEN"
+ }
+
+ let fields = [
+ "index",
+ "url",
+ "used"
+ ]
+
+ let checkID = () => {
+ let split = id.split("-")
+ return (
+ split[0] === idTraits.firstWord
+ )
+ }
+ let idres = checkID()
+ if(!idres) {
+ return false
+ }
+
+ let checkFields = () => {
+ for(let i = 0; i < fields.length; i++) {
+ if(!node[fields[i]]) {
+ throw new Error(`Token ${node.email} is missing trait ${fields[i]}`)
+ return false
+ } else {
+ return true
+ }
+ }
+ }
+ let fieldres = checkFields()
+ if(!fieldres) {
+ return false
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index e4f08d7..31739c5 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,27 +1,22 @@
-import express from 'express';
-import cors from 'cors'
-import cookieParser from 'cookie-parser'
-import http from 'http'
-import fs from 'fs'
-import chalk from 'chalk'
-import moment from 'moment'
-import path from 'path';
-import { initWebSocket } from './ws.js'
+const express = require('express');
+const cors = require('cors');
+const cookieParser = require('cookie-parser');
+const http = require('http');
+const fs = require('fs');
+const chalk = require('chalk');
+const moment = require('moment');
+const path = require('path');
+import { initWebSocket } from './ws.js'
import Database from "./db/db.js"
import AuthHandler from './auth.js';
import handlers from "./handlers.js";
-// Get __dirname in ES6 environment
-import { fileURLToPath } from 'url';
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
class Server {
db;
auth;
- UIPath = path.join(__dirname, '../ui')
- DBPath = path.join(__dirname, '../db')
+ UIPath = path.join(__dirname, './ui')
+ DBPath = path.join(__dirname, './db')
registerRoutes(router) {
// router.post('/api/location', handlers.updateLocation)
@@ -39,7 +34,7 @@ class Server {
if (!token) {
return res.status(400).json({ error: 'Token is required' });
}
- let fromDB = this.db.get.token(token)
+ let fromDB = this.db.tokens.get(token)
if (!fromDB) {
return res.status(403).json({ error: 'Invalid or expired token' });
} else if(fromDB.used) {
@@ -50,8 +45,12 @@ class Server {
newUserSubmission = (req, res) => {
const { token } = req.query;
- db.submitNewUser(token, req.body)
- return res.status(400).json({ error: 'Haven\t finished this bruh' });
+ try {
+ db.members.add(req.body)
+ return res.redirect(`/signin/?new=true`);
+ } catch(e) {
+ return res.status(e.status).json({ error: 'Error adding new member' });
+ }
}
authMiddleware = (req, res, next) => {
@@ -92,23 +91,14 @@ class Server {
let url = req.url
- let publicPage = () => {
- url = "/index.html"
- let filePath = path.join(this.UIPath, "public", url);
- res.sendFile(filePath, (err) => {
- if (err) {
- console.log("File not found, sending fallback:", filePath);
- res.redirect("/");
- }
- });
- }
-
- let publicFile = () => {
+ let publicSite = () => {
let filePath;
if(url.startsWith("/_")) {
filePath = path.join(this.UIPath, url);
+ } else if(url.includes("75820185")) {
+ filePath = path.join(this.UIPath, "public", url.split("75820185")[1]);
} else {
- filePath = path.join(this.UIPath, "public", url);
+ filePath = path.join(this.UIPath, "public", "index.html");
}
res.sendFile(filePath);
@@ -128,11 +118,7 @@ class Server {
}
if(!this.auth.isLoggedInUser(req, res)) {
- if(!url.includes(".")) {
- publicPage()
- } else {
- publicFile()
- }
+ publicSite()
} else {
privateSite()
}
@@ -142,10 +128,10 @@ class Server {
const formattedDate = moment().format('M.D');
const formattedTime = moment().format('h:mma');
if(req.url.includes("/api/")) {
- console.log(chalk.blue(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
+ console.logclean(chalk.blue(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
} else {
if(req.url === "/")
- console.log(chalk.gray(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
+ console.logclean(chalk.gray(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
}
next();
}
@@ -154,9 +140,9 @@ class Server {
const originalSend = res.send;
res.send = function (body) {
if(res.statusCode >= 400) {
- console.log(chalk.blue( `<-${chalk.red(res.statusCode)}- ${req.method} ${req.url} | ${chalk.red(body)}`));
+ console.logclean(chalk.blue( `<-${chalk.red(res.statusCode)}- ${req.method} ${req.url} | ${chalk.red(body)}`));
} else {
- console.log(chalk.blue(`<-${res.statusCode}- ${req.method} ${req.url}`));
+ console.logclean(chalk.blue(`<-${res.statusCode}- ${req.method} ${req.url}`));
}
originalSend.call(this, body);
};
@@ -184,16 +170,16 @@ class Server {
initWebSocket(server);
const PORT = 3003;
server.listen(PORT, () => {
- console.log("\n")
- console.log(chalk.yellow("*************** Hyperia ***************"))
- console.log(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`));
- console.log(chalk.yellow("***************************************"))
- console.log("\n")
+ console.logclean("\n")
+ console.logclean(chalk.yellow("*************** Hyperia ***************"))
+ console.logclean(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`));
+ console.logclean(chalk.yellow("***************************************"))
+ console.logclean("\n")
});
process.on('SIGINT', async () => {
- console.log(chalk.red('Closing server...'));
- console.log(chalk.green('Database connection closed.'));
+ console.logclean(chalk.red('Closing server...'));
+ console.logclean(chalk.green('Database connection closed.'));
process.exit(0);
});
@@ -201,4 +187,38 @@ class Server {
}
}
+const _log = console.log;
+
+console.logclean = function (...args) {
+ _log.call(console, ...args);
+}
+
+// console.log = function (...args) {
+// // Get the caller location
+// const stack = new Error().stack.split("\n")[2];
+// const match = stack.match(/(\/.*:\d+:\d+)/);
+// let location = match ? match[1] : "unknown";
+
+// // Remove CWD prefix
+// while (location.startsWith("/")) {
+// location = location.slice(1);
+// }
+// location = "/" + location
+
+// let cwd = process.cwd();
+// if (location.startsWith(cwd)) {
+// location = location.slice(cwd.length);
+// if (location.startsWith("/")) location = location.slice(1);
+// }
+
+// _log.call(console, `[${location}]`, ...args);
+// };
+
+global.ServerError = class extends Error {
+ constructor(status, msg) {
+ super(msg);
+ this.status = status;
+ }
+}
+
const server = new Server()
\ No newline at end of file
diff --git a/server/ws.js b/server/ws.js
index 2cf8142..8dd88e9 100644
--- a/server/ws.js
+++ b/server/ws.js
@@ -1,4 +1,4 @@
-import { WebSocket, WebSocketServer } from 'ws';
+const { WebSocket, WebSocketServer } = require('ws');
let wss;
diff --git a/ui/public/components/SignupForm.js b/ui/public/components/SignupForm.js
index 2066b6a..73904bf 100644
--- a/ui/public/components/SignupForm.js
+++ b/ui/public/components/SignupForm.js
@@ -15,11 +15,11 @@ class SignupForm extends Shadow {
VStack(() => {
input("First Name")
- .attr({name: "firstname", type: "name"})
+ .attr({name: "firstName", type: "name"})
.styles(this.inputStyles)
input("Last Name")
- .attr({name: "lastname", type: "name"})
+ .attr({name: "lastName", type: "name"})
.styles(this.inputStyles)
input("Email")
diff --git a/ui/public/index.html b/ui/public/index.html
index 28a9064..a06b9c2 100644
--- a/ui/public/index.html
+++ b/ui/public/index.html
@@ -4,17 +4,17 @@
Hyperia
-
+
-
-
+
+
diff --git a/ui/public/pages/Home.js b/ui/public/pages/Home.js
index 6456b23..bdff080 100644
--- a/ui/public/pages/Home.js
+++ b/ui/public/pages/Home.js
@@ -12,7 +12,7 @@ class Home extends Shadow {
NavBar()
- img("_/icons/logo.svg", "2.5em")
+ img("/_/icons/logo.svg", "2.5em")
.onClick((done) => {
if(!done) return
window.navigateTo("/")
@@ -24,7 +24,7 @@ class Home extends Shadow {
switch(window.location.pathname) {
case "/":
- img("_/images/knight.png", "29vmax")
+ img("/_/images/knight.png", "29vmax")
.position("absolute")
.left(50, vw).top(50, vh)
.center()
@@ -51,13 +51,12 @@ class Home extends Shadow {
case "/join":
Join()
break;
- case "/signin":
- SignIn()
- break;
default:
if(window.location.pathname.startsWith("/signup")) {
SignupForm()
+ } else if(window.location.pathname.startsWith("/signin")) {
+ SignIn()
}
}
diff --git a/ui/public/pages/SIgnIn.js b/ui/public/pages/SIgnIn.js
index cf93d85..b936589 100644
--- a/ui/public/pages/SIgnIn.js
+++ b/ui/public/pages/SIgnIn.js
@@ -8,6 +8,12 @@ class SignIn extends Shadow {
render() {
ZStack(() => {
+ if(window.location.search.includes("new")) {
+ p("Welcome to Hyperia! You may now log in.")
+ .x(50, vw).y(40, vh)
+ .center()
+ }
+
form(() => {
input("Email")
.attr({name: "email", type: "email"})