working with db, docker working, small error with personal data

This commit is contained in:
metacryst
2026-03-10 19:09:47 -05:00
parent 4d2c515b7d
commit 7cfcc01c99
25 changed files with 337 additions and 469 deletions

6
Dockerfile Normal file
View 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
View 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
View 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
View 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:

View File

@@ -6,8 +6,8 @@
"start": "node server/index.js"
},
"workspaces": [
"apps/*"
],
"apps/*"
],
"dependencies": {
"argon2": "^0.44.0",
"chalk": "^4.1.2",
@@ -18,8 +18,9 @@
"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"
}
}
}

View File

@@ -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;

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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}
`;
}
}

View File

@@ -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])
}

View File

@@ -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

View File

@@ -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),

View File

@@ -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) : [];
}
}

View File

@@ -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!")
}
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!");
async update(id, data) {
if (data.id || data.created) {
throw new Error("Can't update id or created time!");
}
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;
}
}

View File

@@ -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}`]]
}
}

View File

@@ -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
View 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;

View File

@@ -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)

View File

@@ -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 => ({

View File

@@ -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

View File

@@ -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)

View File

@@ -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")) {

View File

@@ -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,

View File

@@ -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) {