CRUD operations for Posts

- added all CRUD operations for Post model
- add() also creates POST_BY_MEMBER and POST_FROM_NETWORK edges
- delete() also deletes associated edges
- delete() currently does not reallocate node/edge indices after deleting
- will write the data to the db.json file and global.db.nodes
This commit is contained in:
2026-02-08 22:34:18 -05:00
parent d6520bf007
commit aaf9d56b1b
10 changed files with 342 additions and 35 deletions

View File

@@ -63,6 +63,39 @@ export default class Database {
} }
} }
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
}
}
editNode(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) { addEdge(prefix, edge) {
try { try {
let type = prefix let type = prefix
@@ -83,7 +116,22 @@ export default class Database {
throw new global.ServerError(400, "Invalid Data!"); throw new global.ServerError(400, "Invalid Data!");
} }
} else { } else {
throw new Error("Type does not exist for edge: " + id) throw new Error("Type does not exist for edge: " + prefix)
}
} catch(e) {
throw e
}
}
deleteEdge(prefix, id) {
try {
let model = edgeModels[prefix]
if(model) {
console.log(model.indices[0] + (id - 1))
this.edges[model.indices[0] + (id - 1)] = 0
console.log(this.edges[model.indices[0] + (id - 1)])
} else {
throw new Error("Type does not exist for edge: " + prefix)
} }
} catch(e) { } catch(e) {
throw e throw e

View File

@@ -1,13 +1,21 @@
export default class Edge { export default class Edge {
add(n) { splitEdge(n) {
let toPrefix = n.to.split("-")[0] let toPrefix = n.to.split("-")[0]
let fromPrefix = n.from.split("-")[0] let fromPrefix = n.from.split("-")[0]
let type = n.type let type = n.type
let prefix = `${fromPrefix}_${type}_${toPrefix}` return `${fromPrefix}_${type}_${toPrefix}`
}
add(n) {
let prefix = this.splitEdge(n)
global.db.addEdge(prefix, n) global.db.addEdge(prefix, n)
} }
delete(n) {
let prefix = this.splitEdge(n)
global.db.deleteEdge(prefix, n.from.split("-")[1])
}
getByFrom(fromID) { getByFrom(fromID) {
let result = [] let result = []
for(let i = 0; i < this.entries.length; i++) { for(let i = 0; i < this.entries.length; i++) {

View File

@@ -0,0 +1,37 @@
import { z } from 'zod'
export default class POST_BY_MEMBER {
prefix = "POST_BY_MEMBER"
indices = null
constructor(indices) {
this.indices = indices
}
schema = z.object({
id: z.number(),
type: z.string(),
from: z.string(),
to: z.string(),
created: z.string()
})
.strict()
getAuthorId(postId) {
for (let i = this.indices[0]; i < this.indices[1]; i++) {
if (global.db.edges[i].from === postId) {
return Number(global.db.edges[i].to.split("-")[1]);
}
}
}
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
}
}

View File

@@ -0,0 +1,29 @@
import { z } from 'zod'
export default class POST_FROM_NETWORK {
prefix = "POST_FROM_NETWORK"
indices = null
constructor(indices) {
this.indices = indices
}
schema = z.object({
id: z.number(),
type: z.string(),
from: z.string(),
to: z.string(),
created: z.string()
})
.strict()
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
}
}

View File

@@ -1,6 +1,7 @@
import { z } from 'zod'; import { z } from 'zod';
export default class Post { export default class Post {
prefix = "POST"
indices = null indices = null
constructor(indices) { constructor(indices) {
@@ -8,13 +9,16 @@ export default class Post {
} }
schema = z.object({ schema = z.object({
id: z.number(),
text: z.string(), text: z.string(),
time: z.string(), time: z.string(),
sentBy: z.string() sentBy: z.string(),
edited: z.boolean()
}) })
makeID(forum, number) { makeID(forum, number) {
return `POST-${forum}-${number}` // return `POST-${forum}-${number}`
return `POST-${number}`
} }
save(post, id) { save(post, id) {
@@ -32,30 +36,130 @@ export default class Post {
} }
} }
get(forum, number) { get(forum, by, authorId = null) {
let result = [] let result = []
let limit = Math.min(number, this.entries.length) if (by === "member" && authorId) {
for(let i=1; i<=limit; i++) { result = this.getByMember(authorId)
let id = this.makeID(forum, i) } else { // network
let post = this.entries[this.ids[id]] let { id: networkId } = global.db.networks.getByAbbreviation(forum)
let {firstName, lastName} = global.db.members.get(post.sentBy) result = this.getByNetwork(networkId)
let seededObj = {
...post
}
seededObj.sentByID = post.sentBy
seededObj.sentBy = firstName + " " + lastName
result.push(seededObj)
} }
return result return result
} }
getByID(id) {
if(typeof id === 'string') {
id = id.split("-")[1]
}
return {
id,
authorId: this.getAuthor(id),
...global.db.nodes[this.indices[0] + (id - 1)]
}
}
getAuthor(postId) {
return db.POST_BY_MEMBER.getAuthorId(`${this.prefix}-${postId}`);
}
getByNetwork(id) {
let connections = db.POST_FROM_NETWORK.getByNetwork(id)
let posts = []
connections.forEach((conn) => {
posts.push(this.getByID(conn.from))
})
return posts
}
getByMember(stringID) {
let connections = db.POST_BY_MEMBER.getByMember(stringID)
let posts = []
connections.forEach((conn) => {
posts.push(this.getByID(conn.from))
})
return posts
}
async edit(id, text, userEmail) {
try {
let userId = global.db.members.getByEmail(userEmail).id
let postToEdit = this.getByID(id)
if (postToEdit.authorId !== userId) {
throw new global.ServerError(401, "Not authorized to delete post!")
}
postToEdit.text = text
postToEdit.time = global.currentTime()
postToEdit.edited = true;
let { authorId, ...rest } = postToEdit
global.db.editNode(this.prefix, id, rest)
return postToEdit
} catch(e) {
console.error(e)
throw new global.ServerError(400, "Failed to delete post!")
}
}
async delete(id, forum, userEmail) {
try {
let userId = global.db.members.getByEmail(userEmail).id
let network = global.db.networks.getByAbbreviation(forum)
if (this.getAuthor(id) !== userId) {
throw new global.ServerError(401, "Not authorized to delete post!")
}
global.db.deleteNode(this.prefix, id)
if(network) {
global.db.edge.delete({
type: "FROM",
from: `${this.prefix}-${id}`,
to: `NETWORK-${network.id}`
})
}
global.db.edge.delete({
type: "BY",
from: `${this.prefix}-${id}`,
to: `MEMBER-${userId}`
})
} catch(e) {
console.error(e)
throw new global.ServerError(400, "Failed to delete post!")
}
}
async add(text, forum, userEmail) { async add(text, forum, userEmail) {
let newPost = {} let newPost = {}
newPost.text = text let sender = global.db.members.getByEmail(userEmail)
newPost.sentBy = db.members.getIDFromEmail(userEmail) let network = global.db.networks.getByAbbreviation(forum)
newPost.time = global.currentTime()
let idNumber = this.entries.length+1 newPost.text = text
super.add(this.makeID(forum, idNumber), newPost) newPost.time = global.currentTime()
try {
newPost.sentBy = `${sender.firstName} ${sender.lastName}`
global.db.addNode(this.prefix, newPost)
if(network) {
global.db.edge.add({
type: "FROM",
from: `${this.prefix}-${global.db.getCurrentIndex(this)}`,
to: `NETWORK-${network.id}`
})
}
global.db.edge.add({
type: "BY",
from: `${this.prefix}-${global.db.getCurrentIndex(this)}`,
to: `MEMBER-${sender.id}`
})
return `${this.prefix}-${global.db.getCurrentIndex(this)}`
} catch(e) {
console.error(e)
throw new global.ServerError(400, "Failed to add member!");
}
} }
} }

View File

@@ -6,6 +6,8 @@ import Post from "./forum/post.js"
import Conversation from "./dms/conversation.js" import Conversation from "./dms/conversation.js"
import DM from "./dms/dm.js" import DM from "./dms/dm.js"
import MEMBER_IN_NETWORK from "./edges/MEMBER_IN_NETWORK.js" import MEMBER_IN_NETWORK from "./edges/MEMBER_IN_NETWORK.js"
import POST_FROM_NETWORK from "./edges/POST_FROM_NETWORK.js"
import POST_BY_MEMBER from "./edges/POST_BY_MEMBER.js"
let nIndices = { let nIndices = {
"MEMBER" : [0, 0], // [id, startIndex, nextEmptyIndex "MEMBER" : [0, 0], // [id, startIndex, nextEmptyIndex
@@ -18,7 +20,9 @@ let nIndices = {
} }
let eIndices = { let eIndices = {
"MEMBER_IN_NETWORK": [0, 0] "MEMBER_IN_NETWORK": [0, 0],
"POST_FROM_NETWORK": [4000, 4000],
"POST_BY_MEMBER": [7000, 7000]
} }
export let nodeModels = { export let nodeModels = {
@@ -32,5 +36,7 @@ export let nodeModels = {
} }
export let edgeModels = { export let edgeModels = {
MEMBER_IN_NETWORK: new MEMBER_IN_NETWORK(eIndices.MEMBER_IN_NETWORK) MEMBER_IN_NETWORK: new MEMBER_IN_NETWORK(eIndices.MEMBER_IN_NETWORK),
POST_FROM_NETWORK: new POST_FROM_NETWORK(eIndices.POST_FROM_NETWORK),
POST_BY_MEMBER: new POST_BY_MEMBER(eIndices.POST_BY_MEMBER)
} }

View File

@@ -24,6 +24,15 @@ export default class Network {
return global.db.nodes[index] return global.db.nodes[index]
} }
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
}
save(n) { save(n) {
let id = `${this.prefix}-${n.id}` let id = `${this.prefix}-${n.id}`
let result = this.schema.safeParse(n) let result = this.schema.safeParse(n)

View File

@@ -47,9 +47,11 @@ export let PAYMENT = z.object({
}) })
export let POST = z.object({ export let POST = z.object({
id: z.number(),
text: z.string(), text: z.string(),
time: z.string(), time: z.string(),
sentBy: z.string() sentBy: z.string(),
edited: z.boolean()
}) })
export let CONVSRSATION = z.object({ export let CONVSRSATION = z.object({

View File

@@ -8,35 +8,99 @@ const sendSchema = z.object({
const getSchema = z.object({ const getSchema = z.object({
forum: z.string(), forum: z.string(),
number: z.number() by: z.string(),
authorId: z.number()
}) })
.strict() .strict()
const deleteSchema = z.object({
forum: z.string(),
id: z.number()
})
.strict()
const putSchema = z.object({
forum: z.string(),
id: z.number(),
text: z.string()
})
.strict()
export default class ForumHandler { export default class ForumHandler {
static handleSend(msg, ws) { static async handleSend(msg, ws) {
try { try {
global.db.posts.add(msg.text, msg.forum, ws.userEmail) let postId = await global.db.posts.add(msg.text, msg.forum, ws.userEmail)
global.Socket.broadcast({event: "new-post", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})}) let newPost = global.db.posts.getByID(postId)
return {success: true} if (newPost) {
global.Socket.broadcast({
event: "new-post",
app: "FORUM",
forum: msg.forum,
msg: newPost
})
return {success: true}
} else {
return {success: false}
}
} catch(e) { } catch(e) {
console.error(e) console.error(e)
} }
} }
static handleGet(msg) { static handleGet(msg) {
let data = global.db.posts.get(msg.forum, msg.number) let data = global.db.posts.get(msg.forum, msg.by, msg.authorId)
return data return data
} }
static handle(operation, msg, ws) { static async handleDelete(msg, ws) {
try {
await global.db.posts.delete(msg.id, msg.forum, ws.userEmail)
global.Socket.broadcast({
event: "deleted-post",
app: "FORUM",
forum: msg.forum,
msg: msg.id
})
return {success: true}
} catch(e) {
console.error(e)
return {success: false}
}
}
static async handlePut(msg, ws) {
try {
let editedPost = await global.db.posts.edit(msg.id, msg.text, ws.userEmail)
console.log(editedPost)
if (editedPost) {
global.Socket.broadcast({
event: "edited-post",
app: "FORUM",
forum: msg.forum,
msg: editedPost
})
}
return {success: true}
} catch(e) {
console.error(e)
return {success: false}
}
}
static async handle(operation, msg, ws) {
switch(operation) { switch(operation) {
case "SEND": case "SEND":
if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleSend(msg, ws) return await this.handleSend(msg, ws)
case "GET": case "GET":
if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleGet(msg) return this.handleGet(msg)
case "DELETE":
if(!deleteSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleDelete(msg, ws)
case "PUT":
if (!putSchema.safeParse(msg).success) throw new Error("Incorrectly formatted ws message!")
return this.handlePut(msg, ws)
} }
} }

View File

@@ -63,7 +63,7 @@ export default class Socket {
// Build a system where the ws obj is updated every time on navigate, so it already has context // Build a system where the ws obj is updated every time on navigate, so it already has context
// this way, we can only send broadcast messages to clients that actually have that app / subapp open // this way, we can only send broadcast messages to clients that actually have that app / subapp open
handleMessage = (msg, ws) => { handleMessage = async (msg, ws) => {
try { try {
const text = msg.toString(); const text = msg.toString();
const req = JSON.parse(text); const req = JSON.parse(text);
@@ -72,7 +72,7 @@ export default class Socket {
let responseData; let responseData;
switch (req.app) { switch (req.app) {
case "FORUM": case "FORUM":
responseData = ForumHandler.handle(req.operation, req.msg, ws) responseData = await ForumHandler.handle(req.operation, req.msg, ws)
break; break;
case "MESSAGES": case "MESSAGES":