"use server" import { v4 as uuidv4 } from "uuid" import { CreatePostResponse, GetAppPostResponse, GetAppPostsResponse, Post, PostVisibility } from "@/types" import { filterOutBadWords } from "./censorship" import { shuffleArray } from "../utils/shuffleArray" const apiUrl = `${process.env.COMMUNITY_API_URL || ""}` const apiToken = `${process.env.COMMUNITY_API_TOKEN || ""}` const appId = `${process.env.COMMUNITY_API_ID || ""}` const secretModerationKey = `${process.env.MODERATION_KEY || ""}` export async function postToCommunity({ prompt = "", model = "", assetUrl = "", }: { prompt: string model: string, assetUrl: string }): Promise { const before = prompt prompt = filterOutBadWords(prompt) if (prompt !== before) { console.log(`user attempted to use bad words! their original prompt is: ${before}`) } // if the community API is disabled, // we don't fail, we just mock if (!apiUrl) { const mockPost: Post = { postId: uuidv4(), appId: "mock", prompt, model, previewUrl: assetUrl, assetUrl, createdAt: new Date().toISOString(), visibility: "normal", upvotes: 0, downvotes: 0 } return mockPost } if (!prompt) { console.error(`cannot call the community API without a prompt, aborting..`) throw new Error(`cannot call the community API without a prompt, aborting..`) } if (!assetUrl) { console.error(`cannot call the community API without an assetUrl, aborting..`) throw new Error(`cannot call the community API without an assetUrl, aborting..`) } try { console.log(`calling POST ${apiUrl}/posts/${appId} with prompt: ${prompt}`) const postId = uuidv4() const post: Partial = { postId, appId, prompt, model, assetUrl } console.log(`target url is: ${ `${apiUrl}/posts/${appId}` }`) const res = await fetch(`${apiUrl}/posts/${appId}`, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${apiToken}`, }, body: JSON.stringify(post), cache: 'no-store', // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache) // next: { revalidate: 1 } }) // Recommendation: handle errors if (res.status !== 201) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } const response = (await res.json()) as CreatePostResponse // console.log("response:", response) return response.post } catch (err) { const error = `failed to post to community: ${err}` console.error(error) throw new Error(error) } } export async function getLatestPosts({ visibility, maxNbPosts = 1000, shuffle = true, }: { visibility?: PostVisibility maxNbPosts?: number shuffle?: boolean }): Promise { let posts: Post[] = [] // if the community API is disabled we don't fail, // we just mock if (!apiUrl) { return posts } try { // console.log(`calling GET ${apiUrl}/posts with renderId: ${renderId}`) // TODO: send the max number of posts const res = await fetch(`${apiUrl}/posts/${appId}/${ visibility || "all" }`, { method: "GET", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${apiToken}`, }, cache: 'no-store', // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache) // next: { revalidate: 1 } }) // console.log("res:", res) // The return value is *not* serialized // You can return Date, Map, Set, etc. // Recommendation: handle errors if (res.status !== 200) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } const response = (await res.json()) as GetAppPostsResponse // console.log("response:", response) const posts: Post[] = Array.isArray(response?.posts) ? response?.posts : [] if (shuffle) { shuffleArray(posts) } else { posts.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)) } return posts.slice(0, maxNbPosts) } catch (err) { // const error = `failed to get posts: ${err}` // console.error(error) // throw new Error(error) return [] } } export async function getPost(postId: string): Promise { // if the community API is disabled we don't fail, // we just mock if (!apiUrl) { throw new Error("community API is not enabled") } try { // console.log(`calling GET ${apiUrl}/posts with renderId: ${renderId}`) const res = await fetch(`${apiUrl}/posts/${appId}/${postId}`, { method: "GET", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${apiToken}`, }, cache: 'no-store', // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache) // next: { revalidate: 1 } }) // console.log("res:", res) // The return value is *not* serialized // You can return Date, Map, Set, etc. // Recommendation: handle errors if (res.status !== 200) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } const response = (await res.json()) as GetAppPostResponse // console.log("response:", response) return response.post } catch (err) { const error = `failed to get post: ${err}` console.error(error) throw new Error(error) } } export async function deletePost({ postId, moderationKey, }: { postId: string moderationKey: string }): Promise { // if the community API is disabled, // we don't fail, we just mock if (!apiUrl) { return false } if (!postId) { console.error(`cannot delete a post without a postId, aborting..`) throw new Error(`cannot delete a post without a postId, aborting..`) } if (!moderationKey) { console.error(`cannot delete a post without a moderationKey, aborting..`) throw new Error(`cannot delete a post without a moderationKey, aborting..`) } if (moderationKey !== secretModerationKey) { console.error(`invalid moderation key, operation denied! please ask an admin for the mdoeration key`) throw new Error(`invalid moderation key, operation denied! please ask an admin for the mdoeration key`) } try { console.log(`calling DELETE ${apiUrl}/posts/${appId}/${postId}`) const res = await fetch(`${apiUrl}/posts/${appId}/${postId}`, { method: "DELETE", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${apiToken}`, }, cache: 'no-store', // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache) // next: { revalidate: 1 } }) // console.log("res:", res) // The return value is *not* serialized // You can return Date, Map, Set, etc. // Recommendation: handle errors if (res.status !== 200) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } const response = (await res.json()) as CreatePostResponse return true } catch (err) { const error = `failed to delete the post: ${err}` console.error(error) throw new Error(error) } }