import { validate as uuidValidate } from "uuid" import express from "express" import { v4 as uuidv4 } from "uuid" import { hasValidAuthorization } from "./utils/hasValidAuthorization.mts" import { initFolders } from "./initFolders.mts" import { getValidNumber } from "./utils/getValidNumber.mts" import { CreatePostResponse, GetAppPostResponse, GetAppPostsResponse, Post, PostVisibility } from "./types.mts" import { savePost } from "./core/savePost.mts" import { getAppPosts } from "./core/getAppPosts.mts" import { deletePost } from "./core/deletePost.mts" import { getPost } from "./core/getPost.mts" import { getValidBoolean } from "./utils/getValidBoolean.mts" initFolders() const app = express() const port = 7860 // fix this error: "PayloadTooLargeError: request entity too large" // there are multiple version because.. yeah well, it's Express! // app.use(bodyParser.json({limit: '50mb'})); //app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); app.use(express.json({limit: '50mb'})); app.use(express.urlencoded({limit: '50mb', extended: true})); app.post("/posts/:appId", async (req, res) => { if (!hasValidAuthorization(req.headers)) { console.log("Invalid authorization") res.status(401) res.write(JSON.stringify({ error: "invalid token" })) res.end() return } const appId = req.params.appId if (!uuidValidate(appId)) { console.error("invalid appId") res.status(400) res.write(JSON.stringify({ error: `invalid appId` })) res.end() return } const postId = `${req.body.postId || uuidv4()}` const prompt = `${req.body.prompt || ""}` const model = `${req.body.model || ""}` const assetUrl = `${req.body.assetUrl || ""}` const previewUrl = `${req.body.previewUrl || assetUrl}` const createdAt = `${req.body.createdAt || new Date().toISOString()}` const visibility = `${req.body.visibility || "normal"}` as PostVisibility const upvotes = getValidNumber(req.body.upvotes, 0, 1e15, 0) const downvotes = getValidNumber(req.body.upvotes, 0, 1e15, 0) if (!uuidValidate(postId)) { console.error(`invalid postId ${postId}`) res.status(400) res.write(JSON.stringify({ error: `invalid postId ${postId}` })) res.end() return } if (!prompt.length) { console.error(`invalid prompt length: cannot be zero`) res.status(400) res.write(JSON.stringify({ error: `invalid prompt length: cannot be zero` })) res.end() return } if (!previewUrl.length) { console.error(`invalid preview URL length: cannot be zero`) res.status(400) res.write(JSON.stringify({ error: `invalid preview URL length: cannot be zero` })) res.end() return } if (!assetUrl.length) { console.error(`invalid asset URL length: cannot be zero`) res.status(400) res.write(JSON.stringify({ error: `invalid asset URL length: cannot be zero` })) res.end() return } const post: Post = { postId, appId, prompt, model, previewUrl, assetUrl, createdAt, visibility, upvotes, downvotes, } try { await savePost(post) console.log(`saved post:`, post) } catch (err) { console.error(`failed to save the post: ${err}`) res.status(400) res.write(JSON.stringify({ success: false, error: `failed to save the post: ${err}`, post: undefined } as unknown as CreatePostResponse )) res.end() return } res.status(201) res.write(JSON.stringify({ success: true, error: "", post } as CreatePostResponse)) res.end() }) app.get("/posts/:appId/firehose/:visibility/:maxNbItems/:shuffle", async (req, res) => { const appId = `${req.params.appId}` if (!uuidValidate(appId)) { console.error("invalid appId") res.status(400) res.write(JSON.stringify({ error: `invalid appId` })) res.end() return } const visibility = `${req.params.visibility}` const shuffle = getValidBoolean(`${req.params.shuffle}`, false) const limit = getValidNumber(`${req.params.maxNbItems}`, 1, 80, 20) try { const posts = await getAppPosts({ appId, visibility: visibility === "all" ? undefined : visibility as PostVisibility, shuffle, limit }) res.status(200) console.log(`returning ${posts.length} community posts for app ${appId} (visibility: ${visibility})`) res.write(JSON.stringify({ posts } as GetAppPostsResponse)) res.end() return } catch (err) { const error = `failed to load the posts for app ${appId} and visibility ${visibility}: ${err}` console.error(error) res.status(500) res.write(JSON.stringify({ posts: [], error } as GetAppPostsResponse)) res.end() return } }) app.get("/posts/:appId/:postId", async (req, res) => { const appId = `${req.params.appId}` if (!uuidValidate(appId)) { console.error("invalid appId") res.status(400) res.write(JSON.stringify({ error: `invalid appId` })) res.end() return } const postId = `${req.params.postId}` if (!uuidValidate(postId)) { console.error("invalid postId") res.status(400) res.write(JSON.stringify({ error: `invalid postId` })) res.end() return } try { const post = await getPost(appId, postId) res.status(200) console.log(`returning post ${postId} for app ${appId}`) res.write(JSON.stringify({ post } as GetAppPostResponse)) res.end() return } catch (err) { const error = `failed to load the post for app ${appId} and postId: ${postId}: ${err}` console.error(error) res.status(500) res.write(JSON.stringify({ error } as GetAppPostResponse)) res.end() return } }) // delete a post app.delete("/posts/:appId/:postId", async (req, res) => { if (!hasValidAuthorization(req.headers)) { console.log("Invalid authorization") res.status(401) res.write(JSON.stringify({ error: "invalid token" })) res.end() return } const appId = req.params.appId if (!uuidValidate(appId)) { console.error("invalid appId") res.status(400) res.write(JSON.stringify({ error: `invalid appId` })) res.end() return } const postId = req.params.postId if (!uuidValidate(postId)) { console.error("invalid postId") res.status(400) res.write(JSON.stringify({ error: `invalid postId` })) res.end() return } try { await deletePost(appId, postId) res.status(200) res.write(JSON.stringify({ success: true })) res.end() } catch (err) { console.error(err) res.status(404) res.write(JSON.stringify({ error: "couldn't delete this post" })) res.end() } }) app.get("/", async (req, res) => { // this is what users will see in the space - but no need to show something scary res.status(200) res.write(` Community is a micro-service used to manage community posts for my various spaces. It is used by Panoremix, a generative panorama app. `) res.end() }) app.listen(port, () => { console.log(`Open http://localhost:${port}`) })