working with db, docker working, small error with personal data
This commit is contained in:
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM node:18-alpine
|
||||
WORKDIR /usr/src/app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["node", "server/index.js"]
|
||||
58
db/01_init.sql
Normal file
58
db/01_init.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- members
|
||||
CREATE TABLE IF NOT EXISTS members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
apps TEXT[] DEFAULT '{}',
|
||||
created TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- networks
|
||||
CREATE TABLE IF NOT EXISTS networks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
logo TEXT,
|
||||
abbreviation TEXT,
|
||||
apps TEXT[] DEFAULT '{}',
|
||||
stripe_account_id TEXT,
|
||||
stripe_access_token TEXT,
|
||||
created TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- edges (member in network)
|
||||
CREATE TABLE IF NOT EXISTS member_network (
|
||||
id SERIAL PRIMARY KEY,
|
||||
member_id INTEGER REFERENCES members(id),
|
||||
network_id INTEGER REFERENCES networks(id),
|
||||
type TEXT NOT NULL DEFAULT 'IN',
|
||||
created TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(member_id, network_id)
|
||||
);
|
||||
|
||||
-- create schema
|
||||
CREATE SCHEMA IF NOT EXISTS org_1;
|
||||
|
||||
-- join form table
|
||||
CREATE TABLE IF NOT EXISTS org_1.join_form (
|
||||
id SERIAL PRIMARY KEY,
|
||||
fname TEXT NOT NULL,
|
||||
lname TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
county TEXT,
|
||||
time TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- contact form table
|
||||
CREATE TABLE IF NOT EXISTS org_1.contact_form (
|
||||
id SERIAL PRIMARY KEY,
|
||||
fname TEXT NOT NULL,
|
||||
lname TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
message TEXT,
|
||||
county TEXT,
|
||||
time TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
31
db/02_seed.sql
Normal file
31
db/02_seed.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- members
|
||||
INSERT INTO members (email, first_name, last_name, password, apps, created) VALUES
|
||||
('samrussell99@pm.me', 'Sam', 'Russell', '$argon2id$v=19$m=65536,t=3,p=4$n/8BaBisEnBaQNbkxzs1VA$dvvnupWNtB5w5qTBgEciDsNA6rOgXaEypcEK1A0ndLM', '{"Dashboard"}', '2026-01-15 09:58:01.0072'),
|
||||
('freddyjkrueger@gmail.com', 'Freddy','Krueger', '$argon2id$v=19$m=65536,t=3,p=4$ioAYDPtyUulykMrH9W7q9A$lG43cq6Dj3/n1+bJrkupWpB5Xro3UIQaVd9rjuJJ6nM', '{"Dashboard"}', '2026-01-13 13:38:46.0810'),
|
||||
('harmysmarmy@gmail.com', 'Harmy', 'Smarmy', '$argon2id$v=19$m=65536,t=3,p=4$FAhGtCtqNAQ19tBYD73wXQ$0AM/khyBFFuX2mv0ieqtGfsXRgtEldWKFwyeV3BA3Xk', '{"Dashboard"}', '2026-01-13 13:41:41.0722');
|
||||
|
||||
-- networks
|
||||
INSERT INTO networks (name, logo, abbreviation, apps, stripe_account_id, stripe_access_token, created) VALUES
|
||||
('Comal County Young Republicans', 'comalyr.svg', 'comalyr', '{"Dashboard","People","Settings"}', 'acct_1Sn6DwLpyskwAml9', 'sk_test_51Sn6DwLpyskwAml9JzWYlSee2we70EZ2nCwgrEVFGkaPdzXLUBXEDeiMSaVUZtvUowEl5nh07N3CEFYymjDvMXok00vaBYPNgn', '2026-01-10 09:58:01.0074'),
|
||||
('Hyperia', 'hyperia.svg', 'hyperia', '{"Dashboard","People","Settings"}', 'acct_1S4w0GHZemeF9CKR', 'sk_test_51S4w0GHZemeF9CKR2P1B4N7eO51aUcgTuOY5KRiKYXAQXuGaKblXitmBp58fYA200E0N7nHzgZxin1eNkhp2K1xB00q9hFNuQM', '2026-01-10 09:58:01.0074');
|
||||
|
||||
-- member_network edges
|
||||
INSERT INTO member_network (member_id, network_id, type, created) VALUES
|
||||
(1, 1, 'IN', '2025-11-24 00:54:36.0784'),
|
||||
(2, 1, 'IN', '2026-01-13 13:14:28.0178'),
|
||||
(3, 1, 'IN', '2026-01-13 13:28:35.0701'),
|
||||
(1, 2, 'IN', '2026-01-13 13:28:35.0701');
|
||||
|
||||
-- join form seed data
|
||||
INSERT INTO org_1.join_form (fname, lname, email, phone, county, time) VALUES
|
||||
('James', 'Mitchell', 'james.mitchell@gmail.com', '512-555-0101', 'Comal', '2025-12-16 23:11:31.0011'),
|
||||
('Rachel', 'Torres', 'rachel.torres@yahoo.com', '512-555-0102', 'Bexar', '2025-12-19 19:23:12.0717'),
|
||||
('David', 'Nguyen', 'david.nguyen@gmail.com', '830-555-0103', 'Comal', '2026-01-06 16:55:29.0288'),
|
||||
('Emily', 'Sanders', 'emily.sanders@outlook.com', '210-555-0104', 'Hays', '2026-01-07 17:14:01.0711');
|
||||
|
||||
-- contact form seed data
|
||||
INSERT INTO org_1.contact_form (fname, lname, email, phone, county, message, time) VALUES
|
||||
('Marcus', 'Webb', 'marcus.webb@gmail.com', '512-555-0201', 'Comal', 'Interested in volunteering at upcoming events.', '2025-12-29 13:20:28.0157'),
|
||||
('Sandra', 'Holloway', 'sandra.holloway@gmail.com', '830-555-0202', 'Comal', 'Would love to connect with your organization.', '2025-12-30 22:10:24.0971'),
|
||||
('Robert', 'Finley', 'robert.finley@gmail.com', '210-555-0203', 'Comal', 'Looking forward to getting more involved locally.', '2026-01-10 21:23:51.0073'),
|
||||
('Barbara', 'Crane', 'barbara.crane@outlook.com', '512-555-0204', 'Comal', 'Please reach out regarding the next meeting schedule.', '2026-01-10 21:23:54.0841');
|
||||
36
docker-compose.yml
Normal file
36
docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
server:
|
||||
container_name: server
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "10002:10002"
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
- node_modules:/usr/src/app/node_modules
|
||||
- ./db:/usr/src/app/db
|
||||
depends_on:
|
||||
- db
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
- ./db/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
|
||||
- ./db/02_seed.sql:/docker-entrypoint-initdb.d/02_seed.sql
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
node_modules:
|
||||
@@ -6,8 +6,8 @@
|
||||
"start": "node server/index.js"
|
||||
},
|
||||
"workspaces": [
|
||||
"apps/*"
|
||||
],
|
||||
"apps/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"argon2": "^0.44.0",
|
||||
"chalk": "^4.1.2",
|
||||
@@ -18,6 +18,7 @@
|
||||
"express-useragent": "^2.0.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"moment": "^2.30.1",
|
||||
"postgres": "^3.4.8",
|
||||
"stripe": "^20.0.0",
|
||||
"ws": "^8.18.3",
|
||||
"zod": "^4.1.12"
|
||||
|
||||
@@ -42,23 +42,25 @@ export default class AuthHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getProfile(req, res) {
|
||||
async getProfile(req, res) {
|
||||
try {
|
||||
let email = global.auth.getRequestEmail(req, res)
|
||||
|
||||
const user = db.members.getByEmail(email);
|
||||
let connections = db.MEMBER_IN_NETWORK.getByMember(db.members.prefix + "-" + user.id)
|
||||
let userOrgs = connections.map((c) => {
|
||||
let network = db.networks.get(c.to)
|
||||
delete network.stripeAccountId
|
||||
delete network.stripeAccessToken
|
||||
return network
|
||||
})
|
||||
const user = await db.members.getByEmail(email);
|
||||
const connections = await db.MEMBER_IN_NETWORK.getByMember(user.id);
|
||||
|
||||
const userOrgs = await Promise.all(connections.map(async (c) => {
|
||||
const network = await db.networks.get(c.network_id);
|
||||
delete network.stripe_account_id;
|
||||
delete network.stripe_access_token;
|
||||
return network;
|
||||
}));
|
||||
|
||||
res.send({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.firstName + " " + user.lastName,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
networks: userOrgs,
|
||||
apps: user.apps
|
||||
});
|
||||
@@ -70,7 +72,7 @@ export default class AuthHandler {
|
||||
|
||||
async login(req, res) {
|
||||
const { email, password } = req.body;
|
||||
let foundUser = global.db.members.getByEmail(email)
|
||||
let foundUser = await global.db.members.getByEmail(email)
|
||||
if(!foundUser) {
|
||||
res.status(400).json({ error: 'Incorrect email.' });
|
||||
return;
|
||||
|
||||
186
server/db/db.js
186
server/db/db.js
@@ -3,16 +3,8 @@ import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
import { nodeModels, edgeModels } from './model/import.js'
|
||||
import Edge from "./model/edge.js"
|
||||
import { fileURLToPath } from "url"
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export default class Database {
|
||||
|
||||
PERSONAL_DATA_PATH = path.join(__dirname, '../../db/personal')
|
||||
|
||||
nodes = new Array(10000).fill(0);
|
||||
edges = new Array(10000).fill(0);
|
||||
|
||||
constructor() {
|
||||
let values = Object.values(nodeModels)
|
||||
for(let i = 0; i < values.length; i++) {
|
||||
@@ -26,183 +18,5 @@ export default class Database {
|
||||
this[key] = eValues[i]
|
||||
}
|
||||
this.edge = new Edge()
|
||||
this.loadData()
|
||||
}
|
||||
|
||||
getCurrentIndex(model) {
|
||||
return model.indices[1] - model.indices[0]
|
||||
}
|
||||
|
||||
getNextIndex(model) {
|
||||
return model.indices[1] - model.indices[0] + 1
|
||||
}
|
||||
|
||||
// add a get in here that returns a safe copy, not the actual reference
|
||||
|
||||
addNode(prefix, node) {
|
||||
try {
|
||||
let model = nodeModels[prefix]
|
||||
if(model) {
|
||||
let toAdd = {
|
||||
id: this.getNextIndex(model),
|
||||
...node,
|
||||
}
|
||||
if(!toAdd.created) toAdd.created = global.currentTime()
|
||||
let schema = model.schema
|
||||
let result = schema.safeParse(toAdd)
|
||||
if(result.success) {
|
||||
this.nodes[model.indices[1]] = toAdd
|
||||
model.indices[1]++;
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Data!");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Type does not exist for node: " + id)
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
deleteNode(prefix, id) {
|
||||
try {
|
||||
let model = nodeModels[prefix]
|
||||
if(model) {
|
||||
this.nodes[model.indices[0] + (id - 1)] = 0
|
||||
} else {
|
||||
throw new Error("Type does not exist for node: " + id)
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
updateNode(prefix, id, toEdit) {
|
||||
try {
|
||||
let model = nodeModels[prefix]
|
||||
if(model) {
|
||||
let schema = model.schema
|
||||
let result = schema.safeParse(toEdit)
|
||||
if(result.success) {
|
||||
this.nodes[model.indices[0] + (id - 1)] = toEdit
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Data!");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Type does not exist for node: " + prefix)
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
addEdge(prefix, edge) {
|
||||
try {
|
||||
let type = prefix
|
||||
let model = edgeModels[type]
|
||||
if(model) {
|
||||
let toAdd = {
|
||||
id: model.indices[1] - model.indices[0] + 1,
|
||||
...edge
|
||||
}
|
||||
if(!toAdd.created) toAdd.created = global.currentTime()
|
||||
let schema = model.schema
|
||||
let result = schema.safeParse(toAdd)
|
||||
if(result.success) {
|
||||
this.edges[model.indices[1]] = toAdd
|
||||
model.indices[1]++;
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Data!");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Type does not exist for edge: " + prefix)
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
deleteEdge(prefix, id) {
|
||||
try {
|
||||
let model = edgeModels[prefix]
|
||||
if(model) {
|
||||
this.edges[model.indices[0] + (id - 1)] = 0
|
||||
} else {
|
||||
throw new Error("Type does not exist for edge: " + prefix)
|
||||
}
|
||||
} catch(e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
const dbData = await fs.readFile(path.join(process.cwd(), 'db/db.json'), 'utf8');
|
||||
let dbJson;
|
||||
try {
|
||||
dbJson = JSON.parse(dbData);
|
||||
} catch {
|
||||
dbJson = []
|
||||
}
|
||||
|
||||
let nodes = dbJson["nodes"];
|
||||
let entries = Object.entries(nodes)
|
||||
for(let i=0; i<entries.length; i++) {
|
||||
let entry = entries[i]
|
||||
let id = entry[0]; let node = entry[1];
|
||||
this.addNode(id.split("-")[0], node)
|
||||
}
|
||||
|
||||
let edges = dbJson["edges"];
|
||||
let edgeEntries = Object.entries(edges)
|
||||
for(let i=0; i<edgeEntries.length; i++) {
|
||||
let entry = edgeEntries[i]
|
||||
let id = entry[0]; let node = entry[1];
|
||||
this.addEdge(id.split("-")[0], node)
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
global.db.saveData()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
async saveData() {
|
||||
let data = {
|
||||
"nodes": {},
|
||||
"edges": {}
|
||||
}
|
||||
|
||||
let nModels = Object.values(nodeModels)
|
||||
this.nodes.forEach((entry, i) => {
|
||||
if(entry) {
|
||||
for(let j = 0; j < nModels.length; j++) {
|
||||
let model = nModels[j]
|
||||
let indices = model.indices
|
||||
if(i >= indices[0] && i < indices[1]) {
|
||||
let prefix = model.prefix
|
||||
data.nodes[prefix + "-" + entry.id] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let eModels = Object.values(edgeModels)
|
||||
this.edges.forEach((entry, i) => {
|
||||
if(entry) {
|
||||
for(let j = 0; j < eModels.length; j++) {
|
||||
let model = eModels[j]
|
||||
let indices = model.indices
|
||||
if(i >= indices[0] && i < indices[1]) {
|
||||
let prefix = model.prefix
|
||||
data.edges[prefix + "-" + entry.id] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let string = JSON.stringify(data, null, 4)
|
||||
await fs.writeFile(path.join(process.cwd(), 'db/db.json'), string, "utf8");
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ export default class Conversation {
|
||||
function populateMemberProfilesFromIDs(ids) {
|
||||
let result = []
|
||||
for(let i=0; i<ids.length; i++) {
|
||||
console.warn("conversation.js 43: This call to global.db needs to be awaited")
|
||||
result[i] = global.db.members.get(ids[i])
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -48,6 +48,7 @@ export default class Message {
|
||||
let entry = this.entries[i]
|
||||
if(entry.conversation = convoID) {
|
||||
let userID = entry.from
|
||||
console.warn("dm.js 51: This call to global.db needs to be awaited")
|
||||
let fromUser = global.db.members.get(userID)
|
||||
let newObj = {
|
||||
...entry
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import { z } from 'zod'
|
||||
// server/db/model/edges/MEMBER_IN_NETWORK.js
|
||||
import sql from '../../sql.js';
|
||||
|
||||
export default class MEMBER_IN_NETWORK {
|
||||
prefix = "MEMBER_IN_NETWORK"
|
||||
indices = null
|
||||
async getByMember(memberId) {
|
||||
return await sql`
|
||||
SELECT * FROM member_network WHERE member_id = ${memberId}
|
||||
`;
|
||||
}
|
||||
|
||||
constructor(indices) {
|
||||
this.indices = indices
|
||||
}
|
||||
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
type: z.string(),
|
||||
from: z.string(),
|
||||
to: z.string(),
|
||||
created: z.string()
|
||||
})
|
||||
.strict()
|
||||
|
||||
getByMember(stringID) {
|
||||
let result = []
|
||||
for(let i = this.indices[0]; i < this.indices[1]; i++) {
|
||||
if(global.db.edges[i].from === stringID) {
|
||||
result.push(global.db.edges[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
getByNetwork(id) {
|
||||
let result = []
|
||||
for(let i = this.indices[0]; i < this.indices[1]; i++) {
|
||||
if(global.db.edges[i].to === `${global.db.networks.prefix}-${id}`) {
|
||||
result.push(global.db.edges[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
async getByNetwork(networkId) {
|
||||
return await sql`
|
||||
SELECT * FROM member_network WHERE network_id = ${networkId}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export default class POST_FROM_NETWORK {
|
||||
getByNetwork(id) {
|
||||
let result = []
|
||||
for(let i = this.indices[0]; i < this.indices[1]; i++) {
|
||||
console.warn("POST_FROM_NETWORK.js 23: This call to global.db needs to be awaited")
|
||||
if(global.db.edges[i].to === `${global.db.networks.prefix}-${id}`) {
|
||||
result.push(global.db.edges[i])
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export default class Post {
|
||||
if (by === "member" && authorId) {
|
||||
result = this.getByMember(authorId)
|
||||
} else { // network
|
||||
console.warn("post.js 44: This call to global.db needs to be awaited")
|
||||
let { id: networkId } = global.db.networks.getByAbbreviation(forum)
|
||||
result = this.getByNetwork(networkId)
|
||||
}
|
||||
@@ -82,6 +83,7 @@ export default class Post {
|
||||
|
||||
async edit(id, text, userEmail) {
|
||||
try {
|
||||
console.warn("post.js 86: This call to global.db needs to be awaited")
|
||||
let userId = global.db.members.getByEmail(userEmail).id
|
||||
let postToEdit = this.getByID(id)
|
||||
|
||||
@@ -105,7 +107,9 @@ export default class Post {
|
||||
|
||||
async delete(id, forum, userEmail) {
|
||||
try {
|
||||
console.warn("post.js 110: This call to global.db needs to be awaited")
|
||||
let userId = global.db.members.getByEmail(userEmail).id
|
||||
console.warn("post.js 110: This call to global.db needs to be awaited")
|
||||
let network = global.db.networks.getByAbbreviation(forum)
|
||||
|
||||
if (this.getAuthor(id) !== userId) {
|
||||
@@ -134,7 +138,9 @@ export default class Post {
|
||||
|
||||
async add(text, forum, userEmail) {
|
||||
let newPost = {}
|
||||
console.warn("post.js 141: This call to global.db needs to be awaited")
|
||||
let sender = global.db.members.getByEmail(userEmail)
|
||||
console.warn("post.js 143: This call to global.db needs to be awaited")
|
||||
let network = global.db.networks.getByAbbreviation(forum)
|
||||
|
||||
newPost.text = text
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import Title from "./title.js"
|
||||
import Network from "./network.js"
|
||||
import Member from './member.js'
|
||||
import Payment from "./payment.js"
|
||||
import Post from "./forum/post.js"
|
||||
import Conversation from "./dms/conversation.js"
|
||||
import DM from "./dms/dm.js"
|
||||
@@ -12,8 +10,6 @@ import POST_BY_MEMBER from "./edges/POST_BY_MEMBER.js"
|
||||
let nIndices = {
|
||||
"MEMBER" : [0, 0], // [id, startIndex, nextEmptyIndex
|
||||
"NETWORK" : [100, 100],
|
||||
"TITLE" : [200, 200],
|
||||
"PAYMENT" : [300, 300],
|
||||
"POST" : [400, 400],
|
||||
"CONVERSATION" : [3000, 3000],
|
||||
"DM" : [6000, 6000],
|
||||
@@ -28,8 +24,6 @@ let eIndices = {
|
||||
export let nodeModels = {
|
||||
MEMBER: new Member(nIndices.MEMBER),
|
||||
NETWORK: new Network(nIndices.NETWORK),
|
||||
TITLE: new Title(nIndices.TITLE),
|
||||
PAYMENT: new Payment(nIndices.PAYMENT),
|
||||
POST: new Post(nIndices.POST),
|
||||
CONVERSATION: new Conversation(nIndices.CONVERSATION),
|
||||
DM: new DM(nIndices.DM),
|
||||
|
||||
@@ -2,80 +2,84 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import argon2 from 'argon2';
|
||||
import { z } from 'zod';
|
||||
import sql from '../sql.js';
|
||||
|
||||
export default class Member {
|
||||
prefix = "MEMBER"
|
||||
indices = null
|
||||
schema = z.object({
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
password: z.string(),
|
||||
apps: z.array(z.string())
|
||||
});
|
||||
|
||||
constructor(indices) {
|
||||
this.indices = indices
|
||||
isHashed = (s) => s.startsWith("$argon2");
|
||||
|
||||
async add(newMember, networkId = null) {
|
||||
if (!this.isHashed(newMember.password)) {
|
||||
newMember.password = await argon2.hash(newMember.password);
|
||||
}
|
||||
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
password: z.string(),
|
||||
apps: z.array(z.string()),
|
||||
created: z.string()
|
||||
})
|
||||
|
||||
isHashed = (s) => {return s.startsWith("$argon2")}
|
||||
|
||||
async add(newMember, network = null) {
|
||||
const hash = await argon2.hash(newMember.password);
|
||||
newMember.password = hash
|
||||
newMember.apps = ["Dashboard"]
|
||||
|
||||
try {
|
||||
global.db.addNode(this.prefix, newMember)
|
||||
if(network) {
|
||||
global.db.edge.add({
|
||||
type: "IN",
|
||||
from: `${this.prefix}-${global.db.getCurrentIndex(this)}`,
|
||||
to: "NETWORK-1"
|
||||
})
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw new global.ServerError(400, "Failed to add member!");
|
||||
}
|
||||
const result = this.schema.safeParse(newMember);
|
||||
if (!result.success) {
|
||||
console.error(result.error);
|
||||
throw new global.ServerError(400, "Invalid Member Data!");
|
||||
}
|
||||
|
||||
async getPersonalData(id) {
|
||||
const filePath = path.join(global.db.PERSONAL_DATA_PATH, id, "db.json");
|
||||
try {
|
||||
const [member] = await sql`
|
||||
INSERT INTO members ${sql({
|
||||
email: newMember.email,
|
||||
first_name: newMember.firstName,
|
||||
last_name: newMember.lastName,
|
||||
password: newMember.password,
|
||||
apps: newMember.apps ?? ['Dashboard']
|
||||
})}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const [raw] = await Promise.all([
|
||||
fs.promises.readFile(filePath, "utf8"),
|
||||
]);
|
||||
if (networkId) {
|
||||
await sql`
|
||||
INSERT INTO member_network ${sql({
|
||||
member_id: member.id,
|
||||
network_id: networkId,
|
||||
type: 'IN'
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
const result = raw.trim() ? JSON.parse(raw) : [];
|
||||
return result
|
||||
return member;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new global.ServerError(400, "Failed to add member!");
|
||||
}
|
||||
}
|
||||
|
||||
getByNetwork(id) {
|
||||
let connections = db.MEMBER_IN_NETWORK.getByNetwork(id)
|
||||
let members = []
|
||||
connections.forEach((conn) => {
|
||||
members.push(this.getByID(conn.from))
|
||||
})
|
||||
return members
|
||||
}
|
||||
async getByID(id) {
|
||||
const [member] = await sql`
|
||||
SELECT * FROM members WHERE id = ${id}
|
||||
`;
|
||||
return member ?? null;
|
||||
}
|
||||
|
||||
getByID(id) {
|
||||
if(typeof id === 'string') {
|
||||
id = id.split("-")[1]
|
||||
}
|
||||
return global.db.nodes[this.indices[0] + id - 1]
|
||||
}
|
||||
async getByEmail(email) {
|
||||
const [member] = await sql`
|
||||
SELECT * FROM members WHERE email = ${email}
|
||||
`;
|
||||
return member ?? null;
|
||||
}
|
||||
|
||||
getByEmail(email) {
|
||||
for(let i=this.indices[0]; i<this.indices[1]; i++) {
|
||||
if(global.db.nodes[i].email === email) {
|
||||
return global.db.nodes[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
async getByNetwork(networkId) {
|
||||
return await sql`
|
||||
SELECT m.* FROM members m
|
||||
JOIN member_network mn ON mn.member_id = m.id
|
||||
WHERE mn.network_id = ${networkId}
|
||||
`;
|
||||
}
|
||||
|
||||
async getPersonalData(id) {
|
||||
const filePath = path.join(global.db.PERSONAL_DATA_PATH, id, "db.json");
|
||||
const raw = await fs.promises.readFile(filePath, "utf8");
|
||||
return raw.trim() ? JSON.parse(raw) : [];
|
||||
}
|
||||
}
|
||||
@@ -1,78 +1,61 @@
|
||||
import { z } from 'zod';
|
||||
import sql from '../sql.js';
|
||||
|
||||
export default class Network {
|
||||
prefix = `NETWORK`
|
||||
indices = null
|
||||
|
||||
constructor(indices) {
|
||||
this.indices = indices
|
||||
}
|
||||
|
||||
export default class Network {
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
apps: z.array(z.string()),
|
||||
logo: z.string(),
|
||||
abbreviation: z.string(),
|
||||
created: z.string(),
|
||||
stripeAccountId: z.string().nullable(),
|
||||
stripeAccessToken: z.string().nullable()
|
||||
})
|
||||
.strict()
|
||||
}).strict();
|
||||
|
||||
get(id) {
|
||||
if(id.length > 1) {
|
||||
id = id.split("-")[1]
|
||||
}
|
||||
let index = this.indices[0] + (id - 1)
|
||||
return structuredClone(global.db.nodes[index])
|
||||
async get(id) {
|
||||
const [network] = await sql`
|
||||
SELECT * FROM networks WHERE id = ${id}
|
||||
`;
|
||||
return network ?? null;
|
||||
}
|
||||
|
||||
getByAbbreviation(abbreviation) {
|
||||
for(let i=this.indices[0]; i<this.indices[1]; i++) {
|
||||
if(global.db.nodes[i].abbreviation === abbreviation) {
|
||||
return global.db.nodes[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
async getByAbbreviation(abbreviation) {
|
||||
const [network] = await sql`
|
||||
SELECT * FROM networks WHERE abbreviation = ${abbreviation}
|
||||
`;
|
||||
return network ?? null;
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
if(data.id || data.created) {
|
||||
throw new Error("Can't update node id or created time!")
|
||||
async update(id, data) {
|
||||
if (data.id || data.created) {
|
||||
throw new Error("Can't update id or created time!");
|
||||
}
|
||||
let currentObject = this.get(id)
|
||||
let newObject = {...currentObject, ...data}
|
||||
|
||||
try {
|
||||
global.db.updateNode(this.prefix, newObject.id, newObject)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw new global.ServerError(400, "Failed to add member!");
|
||||
}
|
||||
const [updated] = await sql`
|
||||
UPDATE networks SET ${sql(data, ...Object.keys(data))}
|
||||
WHERE id = ${id}
|
||||
RETURNING *
|
||||
`;
|
||||
return updated;
|
||||
}
|
||||
|
||||
save(n) {
|
||||
let id = `${this.prefix}-${n.id}`
|
||||
let result = this.schema.safeParse(n)
|
||||
if(result.success) {
|
||||
try {
|
||||
super.add(id, n)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Member Data!");
|
||||
async add(n) {
|
||||
let result = this.schema.safeParse(n);
|
||||
if (!result.success) {
|
||||
console.error(result.error);
|
||||
throw new global.ServerError(400, "Invalid Network Data!");
|
||||
}
|
||||
}
|
||||
|
||||
add(n) {
|
||||
let toSave = {
|
||||
id: this.entries.length+1,
|
||||
...n
|
||||
}
|
||||
this.save(toSave)
|
||||
const [network] = await sql`
|
||||
INSERT INTO networks ${sql({
|
||||
name: n.name,
|
||||
apps: n.apps,
|
||||
logo: n.logo,
|
||||
abbreviation: n.abbreviation,
|
||||
stripe_account_id: n.stripeAccountId,
|
||||
stripe_access_token: n.stripeAccessToken
|
||||
})}
|
||||
RETURNING *
|
||||
`;
|
||||
return network;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export default class Payment {
|
||||
prefix = "PAYMENT"
|
||||
indices = null
|
||||
|
||||
constructor(indices) {
|
||||
this.indices = indices
|
||||
}
|
||||
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
time: z.string(),
|
||||
amount: z.number(),
|
||||
product: z.string(),
|
||||
})
|
||||
|
||||
save(payment) {
|
||||
let id = `${this.prefix}-${payment.id}`
|
||||
let result = this.schema.safeParse(payment)
|
||||
if(result.success) {
|
||||
try {
|
||||
super.add(id, payment)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Member Data!");
|
||||
}
|
||||
}
|
||||
|
||||
add(paymentObj) {
|
||||
let toSave = {
|
||||
id: this.entries.length+1,
|
||||
...paymentObj
|
||||
}
|
||||
this.save(toSave)
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this.entries[this.ids[`${this.prefix}-${id}`]]
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export default class Title {
|
||||
prefix = `TITLE`
|
||||
indices = null
|
||||
|
||||
constructor(indices) {
|
||||
this.indices = indices
|
||||
}
|
||||
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string()
|
||||
})
|
||||
|
||||
save(newTitle) {
|
||||
let id = `HY-${this.entries.length+1}`
|
||||
if(this.validate(id, newTitle)) {
|
||||
try {
|
||||
global.db.add(id, newTitle)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
throw new global.ServerError(400, "Invalid Member Data!");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
server/db/sql.js
Normal file
8
server/db/sql.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// server/db/sql.js
|
||||
import postgres from 'postgres';
|
||||
import 'dotenv/config'
|
||||
|
||||
console.log("url: ", process.env.LOCAL_DATABASE_URL)
|
||||
const sql = postgres(process.env.LOCAL_DATABASE_URL);
|
||||
|
||||
export default sql;
|
||||
@@ -16,8 +16,7 @@ import Socket from './ws/ws.js'
|
||||
import Database from "./db/db.js"
|
||||
import AuthHandler from './auth.js';
|
||||
import PaymentsHandler from "./payments.js"
|
||||
|
||||
import parchment from '../apps/parchment/index.js';
|
||||
import sql from "./db/sql.js"
|
||||
|
||||
class Server {
|
||||
db;
|
||||
@@ -64,21 +63,14 @@ class Server {
|
||||
try {
|
||||
const networkId = req.params[0]
|
||||
|
||||
if(networkId === "1") {
|
||||
const pathOne = path.join(this.ComalPath, "db", "join.json");
|
||||
const pathTwo = path.join(this.ComalPath, "db", "contact.json");
|
||||
|
||||
const [joinRaw, contactRaw] = await Promise.all([
|
||||
fs.promises.readFile(pathOne, "utf8"),
|
||||
fs.promises.readFile(pathTwo, "utf8")
|
||||
if (networkId === "1") {
|
||||
const [join, contact, members, stripeMembers] = await Promise.all([
|
||||
sql`SELECT * FROM org_1.join_form ORDER BY time DESC`,
|
||||
sql`SELECT * FROM org_1.contact_form ORDER BY time DESC`,
|
||||
db.members.getByNetwork(networkId),
|
||||
payments.getCustomers(networkId)
|
||||
]);
|
||||
|
||||
const join = joinRaw.trim() ? JSON.parse(joinRaw) : [];
|
||||
const contact = contactRaw.trim() ? JSON.parse(contactRaw) : [];
|
||||
const members = db.members.getByNetwork(networkId)
|
||||
let stripeMembers = await payments.getCustomers(networkId)
|
||||
// console.log("stripemenbers: ", stripeMembers)
|
||||
|
||||
res.json({
|
||||
join,
|
||||
contact,
|
||||
@@ -86,7 +78,7 @@ class Server {
|
||||
stripeMembers
|
||||
});
|
||||
} else {
|
||||
const members = db.members.getByNetwork(networkId)
|
||||
const members = await db.members.getByNetwork(networkId)
|
||||
|
||||
res.json({
|
||||
members
|
||||
@@ -225,6 +217,34 @@ class Server {
|
||||
next();
|
||||
}
|
||||
|
||||
async mountApps(app) {
|
||||
const appsDir = path.join(__dirname, '../apps');
|
||||
|
||||
if (!fs.existsSync(appsDir)) {
|
||||
fs.mkdirSync(appsDir, { recursive: true });
|
||||
console.log('created apps directory');
|
||||
return;
|
||||
}
|
||||
|
||||
const appFolders = fs.readdirSync(appsDir);
|
||||
|
||||
for (const folder of appFolders) {
|
||||
const indexPath = path.join(appsDir, folder, 'index.js');
|
||||
if (!fs.existsSync(indexPath)) continue;
|
||||
|
||||
const module = await import(indexPath);
|
||||
const appModule = module.default;
|
||||
|
||||
if (appModule?.type === 'app') {
|
||||
app.use(`/apps/${folder}`, appModule.app);
|
||||
console.log(`mounted app: /apps/${folder}`);
|
||||
} else if (appModule?.type === 'router') {
|
||||
app.use(`/apps/${folder}`, appModule.router);
|
||||
console.log(`mounted router: /apps/${folder}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.db = new Database()
|
||||
this.auth = new AuthHandler()
|
||||
@@ -265,7 +285,7 @@ class Server {
|
||||
app.use(this.logRequest);
|
||||
app.use(this.logResponse);
|
||||
|
||||
app.use('/apps/parchment', parchment.app);
|
||||
this.mountApps(app)
|
||||
|
||||
let router = express.Router();
|
||||
this.registerRoutes(router)
|
||||
|
||||
@@ -19,8 +19,8 @@ export default class PaymentsHandler {
|
||||
await db.networks.update(
|
||||
networkId,
|
||||
{
|
||||
stripeAccountId: stripe_user_id,
|
||||
stripeAccessToken: access_token, // rarely used, long-term access token for the platform
|
||||
stripe_account_id: stripe_user_id,
|
||||
stripe_access_token: access_token, // rarely used, long-term access token for the platform
|
||||
}
|
||||
);
|
||||
|
||||
@@ -28,10 +28,10 @@ export default class PaymentsHandler {
|
||||
}
|
||||
|
||||
static async getProfile(networkId) {
|
||||
let network = global.db.networks.get(networkId)
|
||||
let network = await global.db.networks.get(networkId)
|
||||
if (network) {
|
||||
if (network.stripeAccountId) {
|
||||
const account = await stripe.accounts.retrieve(network.stripeAccountId);
|
||||
if (network.stripe_account_id) {
|
||||
const account = await stripe.accounts.retrieve(network.stripe_account_id);
|
||||
return {
|
||||
chargesEnabled: account.charges_enabled,
|
||||
payoutsEnabled: account.payouts_enabled,
|
||||
@@ -48,13 +48,13 @@ export default class PaymentsHandler {
|
||||
}
|
||||
|
||||
static async getCustomers(networkId) {
|
||||
let network = global.db.networks.get(networkId)
|
||||
if(!network.stripeAccountId) {
|
||||
let network = await global.db.networks.get(networkId)
|
||||
if(!network.stripe_account_id) {
|
||||
throw new Error("Can't get customers for account that doesn't exist!")
|
||||
}
|
||||
const customers = await stripe.customers.list(
|
||||
{ limit: 100, expand: ['data.subscriptions'] },
|
||||
{ stripeAccount: network.stripeAccountId }
|
||||
{ stripeAccount: network.stripe_account_id }
|
||||
);
|
||||
|
||||
return customers.data.map(customer => ({
|
||||
|
||||
@@ -9,6 +9,7 @@ const sendSchema = z.object({
|
||||
export default class MessagesHandler {
|
||||
|
||||
static handleSend(msg, ws) {
|
||||
console.warn("MessagesHandler.js 12: These calls to global.db need to be awaited")
|
||||
let user = global.db.members.getByEmail(ws.userEmail)
|
||||
let convo = global.db.conversations.get(msg.conversation)
|
||||
if(convo.between.includes(`MEMBER-${user.id}`)) {
|
||||
@@ -22,6 +23,7 @@ export default class MessagesHandler {
|
||||
}
|
||||
|
||||
static handleGet(ws) {
|
||||
console.warn("MessagesHandler.js 26: These calls to global.db need to be awaited")
|
||||
let user = global.db.members.getByEmail(ws.userEmail)
|
||||
let data = global.db.conversations.getByMember(`MEMBER-${user.id}`)
|
||||
return data
|
||||
|
||||
@@ -51,7 +51,7 @@ class Dashboard extends Shadow {
|
||||
VStack(() => {
|
||||
|
||||
if(window.location.pathname.startsWith("/my")) {
|
||||
h1(global.profile.name);
|
||||
h1(global.profile.first_name + " " + global.profile.last_name);
|
||||
return
|
||||
}
|
||||
else if(!window.location.pathname.includes("comalyr")) {
|
||||
@@ -150,7 +150,6 @@ class Dashboard extends Shadow {
|
||||
.gap(8);
|
||||
});
|
||||
})
|
||||
.gap(0.5, em)
|
||||
.paddingTop(4, pct)
|
||||
.paddingLeft(5, pct)
|
||||
.width(100, pct)
|
||||
|
||||
@@ -48,7 +48,7 @@ class Dashboard extends Shadow {
|
||||
VStack(() => {
|
||||
|
||||
if(window.location.pathname.startsWith("/my")) {
|
||||
h1(global.profile.name);
|
||||
h1(global.profile.first_name + " " + global.profile.last_name);
|
||||
return
|
||||
}
|
||||
else if(!window.location.pathname.includes("comalyr")) {
|
||||
|
||||
@@ -17,7 +17,7 @@ class People extends Shadow {
|
||||
new gridjs.Grid({
|
||||
columns: ["Name", "Email"],
|
||||
data: global.currentNetwork.data.members.map(m => [
|
||||
m.firstName + " " + m.lastName,
|
||||
m.first_name + " " + m.last_name,
|
||||
m.email
|
||||
]),
|
||||
sort: true,
|
||||
|
||||
@@ -20,7 +20,7 @@ let Global = class {
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
async fetchAppData() {
|
||||
async fetchOrgData() {
|
||||
let personalSpace = this.currentNetwork === this.profile
|
||||
let appData = await fetch(`/api/${personalSpace ? "my" : "org"}data/` + this.currentNetwork.id, {method: "GET"})
|
||||
let json = await appData.json()
|
||||
@@ -84,7 +84,7 @@ let Global = class {
|
||||
}
|
||||
|
||||
if(!this.currentNetwork.data) {
|
||||
this.currentNetwork.data = await this.fetchAppData()
|
||||
this.currentNetwork.data = await this.fetchOrgData()
|
||||
}
|
||||
|
||||
if(appChanged && !networkChanged) {
|
||||
|
||||
Reference in New Issue
Block a user