diff --git a/src/app/api/attachment/route.ts b/src/app/api/attachment/route.ts index 666dc26..cc71211 100644 --- a/src/app/api/attachment/route.ts +++ b/src/app/api/attachment/route.ts @@ -1,167 +1,154 @@ -'use server' +"use server"; -import { APIError, attemptAPIAction } from "@/util/api/error"; -import { sequelize, Bucket, Auth, Post, PostTag, Tag, User, dbSync } from "@/models"; +import { APIError, attemptAPIAction } from "@/util"; +import { + sequelize, + Bucket, + Auth, + Post, + PostTag, + Tag, + User, + dbSync, +} from "@/models"; import { cookies } from "next/headers"; import { Attachment } from "@/models"; -import { UUID } from "crypto"; -import { mkdir, mkdirSync, writeFile } from "fs"; -import { where } from "@sequelize/core"; -import { addToNewBucketForPost } from "../../lib/actions/entityManagement/attachment/attachmentActions"; - -async function writeFilesToFS(uuid: UUID, files: any[]): Promise<{ success: boolean, filePaths: string[] }> { - - type FileArrayEntryWithPromise = { - name: string; - content: Promise> | ReadableStreamReadResult - } - type FileArrayEntry = { - name: string; - content: ReadableStreamReadResult - }; - - // Resolve promises - const fileArray: FileArrayEntryWithPromise[] = await Promise.all(files.map((file: File) => { - return { 'name': (() => file.name)(), 'content': file.stream().getReader().read() }; - })); - - - - let finalFileArray: FileArrayEntry[] = await ( - async () => { - for (let file in fileArray) { - fileArray[file].content = await fileArray[file].content - } - return [...fileArray as FileArrayEntry[]] - })(); - - // Make Bucket Directory - mkdirSync(`./bucket/${uuid}/`, { recursive: true }) - - // Write files to filesystem - for (let file in finalFileArray) { - writeFile(`./bucket/${uuid}/${finalFileArray[file].name}` - , Buffer.from(finalFileArray[file].content.value as Uint8Array) - , (e) => { console.log(e) }); - } - return { - success: true, - filePaths: finalFileArray.map((e) => e.name) - } -} - -async function addToExistingBucket(bucketid: number): Promise { - const bucket = await Bucket.findOne({ - where: { id: bucketid }, include: { association: Bucket.associations.posts } - }); - if (!bucket) throw new APIError({ status: 500, responseText: "invalid bucketid" }); - return bucket; -} - +import { + addToExistingBucket, + addToNewBucketForPost, + writeFilesToFS, +} from "@/app/lib/actions/entityManagement/attachment/attachmentActions"; +import { Attributes } from "@sequelize/core"; +import { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies"; export async function tryCreateAttachment(request: Request) { + // Make sure the DB is ready + await dbSync; - // Make sure the DB is ready - await dbSync; + // Prepare data + const formData = await request.formData(); + const requestData: string | Object | undefined = formData + .get("data") + ?.valueOf(); + const files: FormDataEntryValue[] = formData.getAll("files"); + const authCkie: RequestCookie | undefined = await cookies().get("auth"); - // Prepare data - const formData = await request.formData(); - const requestData: string | Object | undefined = formData.get('data')?.valueOf(); - const files: FormDataEntryValue[] = formData.getAll('files') - const authCkie = await cookies().get("auth"); + // Sanity check auth cookie + if (!authCkie || !authCkie.value) + throw new APIError({ + status: 500, + responseText: "missing auth cookie", + }); - // Sanity check auth cookie - if (!authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" }); + // Get JSON from the Cookie + const cookieJSON: string = authCkie.value; + const authObject:Attributes = JSON.parse(cookieJSON); - // Get JSON from the Cookie - const cookieJSON = authCkie.value; - const authObject = JSON.parse(cookieJSON); + // Fetch User Auth from thse database + const auth = await Auth.findOne({ + include: [ + { + model: User.withScope(["withPerms"]), + attributes: { + exclude: ["username", "password", "updatedAt", "createdAt"], + }, + }, + ], + where: { token: authObject.token }, + }); - // Fetch User Auth from the database - const auth = await Auth.findOne({ - include: [ - { - model: User.withScope(['withPerms']), - attributes: { - exclude: ['username', 'password', 'updatedAt', 'createdAt'] - } - } - ], - where: { token: authObject.token } - }); + // Sanity check the auth and associated user for authorization + if (!auth || !auth.user) + throw new APIError({ + status: 401, + responseText: "Authentication Error", + }); + if (!auth.user.id) + throw new APIError({ status: 401, responseText: "Missing user id" }); + if (!auth.user.perms || !auth.user.perms.isAdmin) + throw new APIError({ status: 401, responseText: `Unauthorized` }); + // Handle incomplete data or other problems + if (!files) + throw new APIError({ status: 500, responseText: "Missing file" }); + if (!formData) + throw new APIError({ status: 500, responseText: "Empty request body" }); + if (!requestData) + throw new APIError({ + status: 500, + responseText: "Missing request data", + }); + if (!(typeof requestData == "string")) + throw new APIError({ + status: 500, + responseText: "Malformed request data", + }); + // Parse JSON + const data = JSON.parse(requestData); - // Sanity check the auth and associated user for authorization - if (!auth - || !auth.user) throw new APIError({ status: 401, responseText: "Authentication Error" }); - if (!auth.user.id) throw new APIError({ status: 401, responseText: "Missing user id" }); - if (!auth.user.perms - || !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized` }); - // Handle incomplete data or other problems - if (!files) throw new APIError({ status: 500, responseText: "Missing file" }); - if (!formData) throw new APIError({ status: 500, responseText: "Empty request body" }); - if (!requestData) throw new APIError({ status: 500, responseText: "Missing request data" }); - if (!(typeof requestData == "string")) throw new APIError({ status: 500, responseText: "Malformed request data" }); + // Get or create bucket + const bucket: Bucket = + data.postid && !data.bucketid + ? await addToNewBucketForPost(data.postid) + : await addToExistingBucket(data.bucketid); + // Write files to bucket and store as attachments in DB + const writeResult = await writeFilesToFS(bucket.id, files); + // Handle failure + if (!writeResult.success) + throw new APIError({ + status: 500, + responseText: "Error writing files to Bucket", + }); - // Parse JSON - const data = JSON.parse(requestData); + // Write attachments to db + const attachments = writeResult.filePaths.map(async (fp) => { + return await Attachment.create( + { + bucket_id: bucket.id, + filename: fp, + }, + { include: Attachment.associations.bucket } + ); + }); + attachments; - // Get or create bucket - const bucket: Bucket = (data.postid && !data.bucketid) - ? await addToNewBucketForPost(data.postid) - : await addToExistingBucket(data.bucketid); - - // Write files to bucket and store as attachments in DB - const writeResult = await writeFilesToFS(bucket.id, files); - // Handle failure - if (!writeResult.success) throw new APIError({ status: 500, responseText: "Error writing files to Bucket" }); - - // Write attachments to db - const attachments = ((writeResult).filePaths.map(async (fp) => { - return (await Attachment.create( - { - bucket_id: bucket.id, - filename: fp - }, - { include: Attachment.associations.bucket } - )); - })) - attachments; - - return new Response( - JSON.stringify({ - bucket: await Bucket.findOne({ - where: { id: bucket.id }, - include: [Bucket.associations.posts, Bucket.associations.attachments] - }) - }), - { status: 200 } - ); + return new Response( + JSON.stringify({ + bucket: await Bucket.findOne({ + where: { id: bucket.id }, + include: [ + Bucket.associations.posts, + Bucket.associations.attachments, + ], + }), + }), + { status: 200 } + ); } export async function tryFetchAttachments(request: Request) { + await Post.sync(); - await Post.sync(); - - const foundPosts = await Post.findAll({ - include: [ - { - association: Post.associations.user, - attributes: { exclude: ['password', 'createdAt', 'updatedAt'] } - }, { - association: Post.associations.postTags - }] - }); - - return new Response(JSON.stringify(foundPosts), { status: 200 }); + const foundPosts = await Post.findAll({ + include: [ + { + association: Post.associations.user, + attributes: { exclude: ["password", "createdAt", "updatedAt"] }, + }, + { + association: Post.associations.postTags, + }, + ], + }); + return new Response(JSON.stringify(foundPosts), { status: 200 }); } export async function GET(request: Request) { - return await attemptAPIAction(tryFetchAttachments, request); + return await attemptAPIAction(tryFetchAttachments, request); } export async function POST(request: Request) { - return await attemptAPIAction(tryCreateAttachment, request); + return await attemptAPIAction(tryCreateAttachment, request); } diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index 482c112..1da075f 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -1,84 +1,73 @@ -'use server' +"use server"; import { cookies } from "next/headers"; - -import { APIError} from "@/util/api/error" -import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user" +import { APIError, UserAuth, parseBasicAuth, getAssociatedUser } from "@/util"; import { Auth, User } from "@/models"; +async function tryAuth(request: Request) { + // await User.sync(); + await Auth.sync(); -async function tryAuth(request:Request){ + const auth: string | null = request.headers.get("authorization"); - // await User.sync(); - await Auth.sync(); + if (!auth) { + return new Response("unauthorized", { status: 403 }); + } - const auth:string|null = request.headers.get("authorization"); + const userAuth = parseBasicAuth(auth); + const user = await getAssociatedUser(userAuth); - if(!auth){ - return new Response("unauthorized",{status:403}); - } + if (!user || !user.id) return new Response("error", { status: 500 }); - const userAuth = parseBasicAuth(auth); - const user = await getAssociatedUser(userAuth); - - if (!user || !user.id) - return new Response("error",{status:500}); + const authentication = await Auth.create({ + user_id: user.id, + }); - const authentication = await Auth.create({ - user_id: user.id, - }) + console.log("ok"); + const foundAuth = await Auth.findOne({ + include: { + model: User, + as: "user", + }, + where: { + user_id: user.id, + }, + }); + console.log("ok2"); - console.log('ok'); - const foundAuth = await Auth.findOne({ - include: { - model: User, - as: 'user' - }, - where: { - user_id: user.id - } - }) - console.log('ok2'); + if (!foundAuth) return new Response("error", { status: 500 }); - if(!foundAuth) - return new Response("error",{status:500}); - - const usr = foundAuth.user; - + const usr = foundAuth.user; + const authUser = await authentication.getUser(); - const authUser = await authentication.getUser(); + // @ts-ignore + cookies().set("auth", JSON.stringify(authentication)); - // @ts-ignore - cookies().set('auth', JSON.stringify(authentication)); - - return new Response( - JSON.stringify( - { - credentials: userAuth, - auth: authentication, - user: authUser, - foundAuth: foundAuth - }), - { - status: 200, - headers:{ - "Content-Type": "text/JSON" - } - } - ); + return new Response( + JSON.stringify({ + credentials: userAuth, + auth: authentication, + user: authUser, + foundAuth: foundAuth, + }), + { + status: 200, + headers: { + "Content-Type": "text/JSON", + }, + } + ); } -export async function POST(request:Request){ - try{ - return await tryAuth(request); - } - catch(e){ - if (e instanceof APIError){ - return new Response(e.info.responseText,{status:e.info.status}); - } - else{ - throw e; - } - } +export async function POST(request: Request) { + try { + return await tryAuth(request); + } catch (e) { + if (e instanceof APIError) { + return new Response(e.info.responseText, { status: e.info.status }); + } else { + throw e; + } + } } diff --git a/src/app/api/setupDB/route.ts b/src/app/api/setupDB/route.ts index 1063b23..83ac650 100644 --- a/src/app/api/setupDB/route.ts +++ b/src/app/api/setupDB/route.ts @@ -7,7 +7,7 @@ import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user" import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms,dbSync,sequelize} from "@/models"; import Sequelize, { CreationAttributes, DataTypes } from "@sequelize/core"; import { SqliteColumnsDescription, SqliteDialect, SqliteQueryInterface } from "@sequelize/sqlite3"; -import { hashPassword } from "@/util/Auth"; +import { hashpassword } from "@/util/auth"; import { copyFile, readFileSync } from "fs"; import path from "path"; import { Attributes } from '@sequelize/core'; @@ -19,7 +19,7 @@ async function seedUsers(qif: SqliteQueryInterface){ const json: {users: CreationAttributes[]} = JSON.parse(Buffer.from(readFileSync(fp).valueOf()).toString()); const users = json.users.map(async user=>{ - user.password = await hashPassword(user.password); + user.password = await hashpassword(user.password); return user; }) diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 0425c1c..15bbc9d 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -11,7 +11,7 @@ import { APIError } from "@/util/api/error"; import { UserAuth } from "@/util/api/user"; import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms, addUserScopes, dbSync,sequelize} from "@/models"; -import { hashPassword } from "@/util/Auth"; +import { hashpassword } from "@/util/auth"; // Attempt to register a new User @@ -32,7 +32,7 @@ async function attemptRegister(request:Request){ // Hash the password and create a new user in the database const user = await User.create({ username: requestBody.username, - password: await hashPassword(requestBody.password), + password: await hashpassword(requestBody.password), perms:{ isAdmin: false } diff --git a/src/app/lib/actions/actions.ts b/src/app/lib/actions/actions.ts index ac20a72..649b66e 100644 --- a/src/app/lib/actions/actions.ts +++ b/src/app/lib/actions/actions.ts @@ -1,6 +1,6 @@ 'use server' -import { constructAPIUrl } from "@/util/Utils" +import { constructAPIUrl } from "@/util/url"; import { cookies } from "next/headers" import { parseSetCookie } from "@/util/parseSetCookie"; import makeFetchCookie from 'fetch-cookie'; diff --git a/src/app/lib/actions/entityManagement/attachment/attachmentActions.ts b/src/app/lib/actions/entityManagement/attachment/attachmentActions.ts index e91a90a..fd9d80b 100644 --- a/src/app/lib/actions/entityManagement/attachment/attachmentActions.ts +++ b/src/app/lib/actions/entityManagement/attachment/attachmentActions.ts @@ -1,13 +1,65 @@ 'use server'; import { APIError } from "@/util/api/error"; import { Bucket, Post } from "@/models"; -import { randomUUID } from "crypto"; +import { randomUUID, UUID } from "crypto"; +import { mkdirSync, writeFile } from "fs"; export async function addToNewBucketForPost(postid: number): Promise { Post.sync(); - const post = await Post.findOne({ where: { id: postid } }); + const post: Post | null = await Post.findOne({ where: { id: postid } }); if (!post) throw new APIError({ status: 500, responseText: "invalid postid" }); const bucket = await post.createBucket({ id: randomUUID() }); return bucket; } + +export async function writeFilesToFS(uuid: UUID, files: any[]): Promise<{ success: boolean, filePaths: string[] }> { + + type FileArrayEntryWithPromise = { + name: string; + content: Promise> | ReadableStreamReadResult + } + type FileArrayEntry = { + name: string; + content: ReadableStreamReadResult + }; + + // Resolve promises + const fileArray: FileArrayEntryWithPromise[] = await Promise.all(files.map((file: File) => { + return { 'name': (() => file.name)(), 'content': file.stream().getReader().read() }; + })); + + + + let finalFileArray: FileArrayEntry[] = await ( + async () => { + for (let file in fileArray) { + fileArray[file].content = await fileArray[file].content + } + return [...fileArray as FileArrayEntry[]] + })(); + + // Make Bucket Directory + mkdirSync(`./bucket/${uuid}/`, { recursive: true }) + + // Write files to filesystem + for (let file in finalFileArray) { + writeFile(`./bucket/${uuid}/${finalFileArray[file].name}` + , Buffer.from(finalFileArray[file].content.value as Uint8Array) + , (e) => { console.log(e) }); + } + return { + success: true, + filePaths: finalFileArray.map((e) => e.name) + } +} + +export async function addToExistingBucket(bucketid: number): Promise { + const bucket = await Bucket.findOne({ + where: { id: bucketid }, + include: { association: Bucket.associations.posts }, + }); + if (!bucket) + throw new APIError({ status: 500, responseText: "invalid bucketid" }); + return bucket; +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 0bf31e1..521bfbf 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,8 +6,8 @@ import ArticlePreview from "@/components/shared/news/article-preview" import ReactDOM from "react"; import "/public/global.css" import "./index.css" -import { Post } from "@/model/Post"; -import { constructAPIUrl } from "@/util/Utils"; +import { Post } from "@/models"; +import { constructAPIUrl } from "@/util"; import Link from "next/link"; import { Attributes } from "@sequelize/core"; diff --git a/src/components/client/admin/PostTable.tsx b/src/components/client/admin/PostTable.tsx index d411c32..72d653b 100644 --- a/src/components/client/admin/PostTable.tsx +++ b/src/components/client/admin/PostTable.tsx @@ -4,7 +4,13 @@ import EntityManagementTable from "../EntityManagementTable"; import toast from "react-hot-toast"; import { EditorRenderer, EditorState, PostTableCallbacks } from "./PostEditor"; import { Attributes, InferAttributes } from "@sequelize/core"; -import { Project, Post, Bucket, User, PostAttributesWithBuckets } from "@/models"; +import { + Project, + Post, + Bucket, + User, + PostAttributesWithBuckets, +} from "@/models"; import { handleActionResult } from "@/app/lib/actions/clientActionHandler"; import { @@ -13,16 +19,15 @@ import { PostServerActions, } from "@/app/lib/actions/entityManagement/post/postActions"; import { PostViewProps } from "@/views/admin/ClientPostView"; -import { aifa } from "@/util/Utils"; -import { StateHook } from "@/util/state/stateUtils"; +import { aifa } from "@/util/utils"; +import { StateHook } from "@/util/state"; export type PostTableStateProps = { - posts:StateHook, - editor:StateHook -} - -export type PostTableProps = PostViewProps & {state:PostTableStateProps}; + posts: StateHook; + editor: StateHook; +}; +export type PostTableProps = PostViewProps & { state: PostTableStateProps }; export default function PostTable({ children, @@ -32,8 +37,6 @@ export default function PostTable({ actions, state, }: PostTableProps) { - - // Define editor controls const editorControls = { closeEditor: () => { @@ -93,10 +96,7 @@ export default function PostTable({ }; return ( - + {state.posts.state.map((post: GetPostsAttributes) => ( diff --git a/src/components/client/admin/loginForm.tsx b/src/components/client/admin/loginForm.tsx index 0b34a4c..919dda7 100644 --- a/src/components/client/admin/loginForm.tsx +++ b/src/components/client/admin/loginForm.tsx @@ -2,7 +2,7 @@ import { serverAttemptAuthenticateUser } from "@/app/lib/actions/actions"; import { AuthContext } from "@/providers/providers"; -import { constructAPIUrl } from "@/util/Utils"; +import { constructAPIUrl } from "@/util/url"; import { createContext, useState } from "react"; import { useFormState, useFormStatus } from "react-dom"; diff --git a/src/components/client/input/EntityEditorTextArea.tsx b/src/components/client/input/EntityEditorTextArea.tsx index 41dd33f..b4b2941 100644 --- a/src/components/client/input/EntityEditorTextArea.tsx +++ b/src/components/client/input/EntityEditorTextArea.tsx @@ -1,20 +1,34 @@ -'use client' +"use client"; -import { useRef, MutableRefObject, useLayoutEffect, ChangeEventHandler, useState } from "react"; -import { StateHook } from '../../../util/types/StateHook'; +import { + useRef, + MutableRefObject, + useLayoutEffect, + ChangeEventHandler, + useState, +} from "react"; +import { StateHook } from "@/util"; -export function EntityEditorTextArea({contentHook, className}: {contentHook:StateHook, className:string}){ +// Autosize the text area +function textAreaAutoSize( + textbox: MutableRefObject +): void { + if (!textbox.current || !textbox.current.style) return; + textbox.current.style.height = "fit-content"; + textbox.current.style.height = `${textbox.current.scrollHeight}px`; +} - let textbox: any = useRef(undefined); +type EntityEditorTextAreaProps = { + contentHook: StateHook; + className: string; +}; + +export function EntityEditorTextArea({ + contentHook, + className, +}: EntityEditorTextAreaProps) { + let textbox: any = useRef(undefined); - // Autosize the text area - function textAreaAutoSize( - textbox: MutableRefObject - ): void { - if (!textbox.current || !textbox.current.style) return; - textbox.current.style.height = "fit-content"; - textbox.current.style.height = `${textbox.current.scrollHeight}px`; - } useLayoutEffect(() => textAreaAutoSize(textbox)); // Handle user input on the text area by updating state and autosizing the textfield @@ -23,12 +37,14 @@ export function EntityEditorTextArea({contentHook, className}: {contentHook:Stat textAreaAutoSize(textbox); // Autosize the text area }; - return