From adc02f2e5b63d616bbfa5dda2fd4827a5e9d27b3 Mon Sep 17 00:00:00 2001 From: Andreas Schaafsma Date: Mon, 17 Jun 2024 15:28:57 +0200 Subject: [PATCH] various changes --- src/app/api/post/route.ts | 7 +- src/app/attachment/[...slug]/route.ts | 8 + src/app/globals.css | 225 +++++++++--------- .../entityManagement/attachmentActions.ts | 0 .../actions/entityManagement/postActions.ts | 8 +- src/app/lib/jsxtostring.ts | 39 +++ src/app/page.tsx | 2 +- src/components/client/admin/PostEditor.tsx | 41 +++- .../server/admin/views/PostView.tsx | 8 +- src/components/shared/brand/header.module.css | 2 +- src/components/shared/brand/header.tsx | 4 +- .../shared/navigation/navbar.module.css | 2 +- src/components/shared/navigation/navbar.tsx | 2 +- .../shared/news/article-preview.tsx | 45 +++- src/components/shared/news/article.tsx | 18 +- .../shared/page-container.module.css | 2 +- src/components/shared/page-container.tsx | 2 +- src/model/Models.ts | 2 +- src/model/Post.ts | 6 +- 19 files changed, 268 insertions(+), 155 deletions(-) create mode 100644 src/app/attachment/[...slug]/route.ts create mode 100644 src/app/lib/actions/entityManagement/attachmentActions.ts create mode 100644 src/app/lib/jsxtostring.ts diff --git a/src/app/api/post/route.ts b/src/app/api/post/route.ts index ef0bdb3..328b239 100644 --- a/src/app/api/post/route.ts +++ b/src/app/api/post/route.ts @@ -7,21 +7,16 @@ import { cookies } from "next/headers"; async function tryCreatePost(request: Request) { - // Make sure the DB is ready const sync = await dbSync; - // Prepare data const requestBody = await request.json(); const authCkie = await cookies().get("auth"); - // Sanity check auth cookie if ( !authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" }); - // Get JSON from the Cookie const cookieJSON = authCkie.value; const authObject = JSON.parse(cookieJSON); - // Fetch User Auth from the database const auth = await Auth.findOne({ include: [ @@ -42,6 +37,7 @@ async function tryCreatePost(request: Request) { if (!requestBody) throw new APIError({ status: 500, responseText: "Empty request body" }); if (!requestBody.title) throw new APIError({ status: 500, responseText: "Missing post title" }); if (!requestBody.content) throw new APIError({ status: 500, responseText: "Missing post content" }); + if (!requestBody.description) throw new APIError({ status: 500, responseText: "Missing post description" }); if (!auth.user.id) throw new APIError({ status: 500, responseText: "Missing user id" }); if (!auth.user.perms || !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized ${JSON.stringify(auth.user)}` }); @@ -49,6 +45,7 @@ async function tryCreatePost(request: Request) { const post = await Post.create( { content: requestBody.content, + description: requestBody.description, user_id: auth.user.id, title: requestBody.title, },{ diff --git a/src/app/attachment/[...slug]/route.ts b/src/app/attachment/[...slug]/route.ts new file mode 100644 index 0000000..299aabd --- /dev/null +++ b/src/app/attachment/[...slug]/route.ts @@ -0,0 +1,8 @@ +import { open, openSync, readFileSync } from "fs"; +import { NextRequest, NextResponse } from "next/server"; +import path from "path"; + +export async function GET(req:NextRequest, { params }: {params:{slug: string}}){ + const fp = path.resolve('.',`bucket`,...params.slug); + return new Response(readFileSync(fp)); +} \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 8e62362..a8ac50a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -28,127 +28,132 @@ body { @layer base { :root { - --text-50: #f3eff5; - --text-100: #e7dfec; - --text-200: #cfbfd9; - --text-300: #b89fc6; - --text-400: #a080b3; - --text-500: #88609f; - --text-600: #6d4d80; - --text-700: #523960; - --text-800: #362640; - --text-900: #1b1320; - --text-950: #0e0a10; + --text: #ffffff; + --text-50: #f2f2f2; + --text-100: #e6e6e6; + --text-200: #cccccc; + --text-300: #b3b3b3; + --text-400: #999999; + --text-500: #808080; + --text-600: #666666; + --text-700: #4d4d4d; + --text-800: #333333; + --text-900: #1a1a1a; + --text-950: #0d0d0d; - --background-50: #f4eef6; - --background-100: #e8ddee; - --background-200: #d2bbdd; - --background-300: #bb99cc; - --background-400: #a477bb; - --background-500: #8e55aa; - --background-600: #714488; - --background-700: #553366; - --background-800: #392244; - --background-900: #1c1122; - --background-950: #0e0911; + --background: #1e546b; + --background-50: #ebf5f9; + --background-100: #d7ebf4; + --background-200: #afd7e9; + --background-300: #88c4dd; + --background-400: #60b0d2; + --background-500: #389cc7; + --background-600: #2d7d9f; + --background-700: #225e77; + --background-800: #163e50; + --background-900: #0b1f28; + --background-950: #061014; - --primary-50: #f3edf8; - --primary-100: #e6daf1; - --primary-200: #ceb5e3; - --primary-300: #b590d5; - --primary-400: #9c6bc7; - --primary-500: #8346b9; - --primary-600: #693894; - --primary-700: #4f2a6f; - --primary-800: #351c4a; - --primary-900: #1a0e25; - --primary-950: #0d0712; + --primary: #008bc7; + --primary-50: #e5f7ff; + --primary-100: #ccf0ff; + --primary-200: #99e0ff; + --primary-300: #66d1ff; + --primary-400: #33c2ff; + --primary-500: #00b2ff; + --primary-600: #008fcc; + --primary-700: #006b99; + --primary-800: #004766; + --primary-900: #002433; + --primary-950: #00121a; - --secondary-50: #f3ebf9; - --secondary-100: #e7d7f4; - --secondary-200: #cfb0e8; - --secondary-300: #b788dd; - --secondary-400: #9f61d1; - --secondary-500: #8739c6; - --secondary-600: #6c2e9e; - --secondary-700: #512277; - --secondary-800: #36174f; - --secondary-900: #1b0b28; - --secondary-950: #0d0614; + --secondary: #136d94; + --secondary-50: #e8f6fc; + --secondary-100: #d2edf9; + --secondary-200: #a5dcf3; + --secondary-300: #78caed; + --secondary-400: #4ab8e8; + --secondary-500: #1da7e2; + --secondary-600: #1785b5; + --secondary-700: #126487; + --secondary-800: #0c435a; + --secondary-900: #06212d; + --secondary-950: #031117; - --accent-50: #f3eafb; - --accent-100: #e7d5f6; - --accent-200: #cfaaee; - --accent-300: #b880e5; - --accent-400: #a056dc; - --accent-500: #882bd4; - --accent-600: #6d23a9; - --accent-700: #521a7f; - --accent-800: #361155; - --accent-900: #1b092a; - --accent-950: #0e0415; + --accent: #42aedc; + --accent-50: #e9f6fb; + --accent-100: #d4edf7; + --accent-200: #a9daef; + --accent-300: #7ec8e7; + --accent-400: #53b5df; + --accent-500: #28a3d7; + --accent-600: #2082ac; + --accent-700: #186281; + --accent-800: #104156; + --accent-900: #08212b; + --accent-950: #041016; } .dark { - --text-50: #0e0910; - --text-100: #1b1320; - --text-200: #362541; - --text-300: #513861; - --text-400: #6c4a82; - --text-500: #885da2; - --text-600: #9f7db5; - --text-700: #b79ec7; - --text-800: #cfbeda; - --text-900: #e7dfec; - --text-950: #f3eff6; + --text-50: #0d0d0d; + --text-100: #1a1a1a; + --text-200: #333333; + --text-300: #4d4d4d; + --text-400: #666666; + --text-500: #808080; + --text-600: #999999; + --text-700: #b3b3b3; + --text-800: #cccccc; + --text-900: #e6e6e6; + --text-950: #f2f2f2; - --background-50: #0d0911; - --background-100: #1b1122; - --background-200: #352244; - --background-300: #503366; - --background-400: #6a4488; - --background-500: #8555aa; - --background-600: #9d77bb; - --background-700: #b699cc; - --background-800: #cebbdd; - --background-900: #e7ddee; - --background-950: #f3eef6; + --background-50: #061014; + --background-100: #0b1f28; + --background-200: #163e50; + --background-300: #225e77; + --background-400: #2d7d9f; + --background-500: #389cc7; + --background-600: #60b0d2; + --background-700: #88c4dd; + --background-800: #afd7e9; + --background-900: #d7ebf4; + --background-950: #ebf5f9; - --primary-50: #0d0712; - --primary-100: #1a0e25; - --primary-200: #351c4a; - --primary-300: #4f2a6f; - --primary-400: #693894; - --primary-500: #8346b9; - --primary-600: #9c6bc7; - --primary-700: #b590d5; - --primary-800: #ceb5e3; - --primary-900: #e6daf1; - --primary-950: #f3edf8; + --primary-50: #00121a; + --primary-100: #002433; + --primary-200: #004766; + --primary-300: #006b99; + --primary-400: #008fcc; + --primary-500: #00b2ff; + --primary-600: #33c2ff; + --primary-700: #66d1ff; + --primary-800: #99e0ff; + --primary-900: #ccf0ff; + --primary-950: #e5f7ff; - --secondary-50: #0d0614; - --secondary-100: #1b0b28; - --secondary-200: #36174f; - --secondary-300: #512277; - --secondary-400: #6c2e9e; - --secondary-500: #8739c6; - --secondary-600: #9f61d1; - --secondary-700: #b788dd; - --secondary-800: #cfb0e8; - --secondary-900: #e7d7f4; - --secondary-950: #f3ebf9; + --secondary-50: #031117; + --secondary-100: #06212d; + --secondary-200: #0c435a; + --secondary-300: #126487; + --secondary-400: #1785b5; + --secondary-500: #1da7e2; + --secondary-600: #4ab8e8; + --secondary-700: #78caed; + --secondary-800: #a5dcf3; + --secondary-900: #d2edf9; + --secondary-950: #e8f6fc; - --accent-50: #0e0415; - --accent-100: #1b092a; - --accent-200: #361155; - --accent-300: #521a7f; - --accent-400: #6d23a9; - --accent-500: #882bd4; - --accent-600: #a056dc; - --accent-700: #b880e5; - --accent-800: #cfaaee; - --accent-900: #e7d5f6; - --accent-950: #f3eafb; + --accent-50: #041016; + --accent-100: #08212b; + --accent-200: #104156; + --accent-300: #186281; + --accent-400: #2082ac; + --accent-500: #28a3d7; + --accent-600: #53b5df; + --accent-700: #7ec8e7; + --accent-800: #a9daef; + --accent-900: #d4edf7; + --accent-950: #e9f6fb; } } \ No newline at end of file diff --git a/src/app/lib/actions/entityManagement/attachmentActions.ts b/src/app/lib/actions/entityManagement/attachmentActions.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/lib/actions/entityManagement/postActions.ts b/src/app/lib/actions/entityManagement/postActions.ts index 3b1b2fc..e296048 100644 --- a/src/app/lib/actions/entityManagement/postActions.ts +++ b/src/app/lib/actions/entityManagement/postActions.ts @@ -1,11 +1,10 @@ 'use server'; import { revalidatePath, revalidateTag } from 'next/cache' -import { Post } from "@/model/Post"; +import { Bucket, Post, PostBucket, User, dbSync } from "@/model/Models"; import { Attributes, where } from "@sequelize/core"; import { cookies } from 'next/headers'; import { getCookieAuth, userIsAdmin } from '../actions'; -import { User } from '@/model/User'; import { ActionResult } from '../ActionResult'; @@ -13,6 +12,7 @@ import { ActionResult } from '../ActionResult'; export async function deletePost(postID: number): Promise> { + await dbSync; // revalidatePath("/admin/man-post","page") if(! await userIsAdmin()) return {error:"Unauthorized, not deleting Post", result: false} const destroy = await Post.destroy({ where: { id: postID } }); @@ -21,12 +21,14 @@ export async function deletePost(postID: number): Promise> export async function getPosts(): Promise[]>> { + await dbSync; if(! await userIsAdmin()) return {error:"Unauthorized, not fetching Posts."} - const posts = await Post.findAll(); + const posts = await Post.findAll({include: {association: Post.associations.postBuckets, include: Bucket.associations.attachments}},); return {result:JSON.parse(JSON.stringify(posts))}; } export async function updatePost(postAttributes: Partial>): Promise[]>> { + await dbSync; if(! await userIsAdmin()) return {error:"Unauthorized, not updating Post."} const post = await Post.update(postAttributes, {where:{id:postAttributes.id}}); return {result:JSON.parse(JSON.stringify(post))}; diff --git a/src/app/lib/jsxtostring.ts b/src/app/lib/jsxtostring.ts new file mode 100644 index 0000000..dbf3ee3 --- /dev/null +++ b/src/app/lib/jsxtostring.ts @@ -0,0 +1,39 @@ +import React, { ReactElement, ReactNode } from 'react'; + +function renderAttributes(props: Record): string { + return Object.keys(props) + .map((key) => { + const value = props[key]; + if (key === 'children' || value === undefined || value === null) { + return ''; + } + return `${key}="${value}"`; + }) + .filter(Boolean) + .join(' '); +} + +function renderElement(element: ReactNode): string { + if (typeof element === 'string' || typeof element === 'number') { + return String(element); + } + + if (React.isValidElement(element)) { + const { type, props } = element; + const mapped = React.Children.map(props.children, renderElement) + const cilds = mapped? mapped.join('') : props.children; + const attributes = renderAttributes(props); + return `<${type}${attributes ? ' ' + attributes : ''}>${cilds}`; + } + + if (Array.isArray(element)) { + return element.map(renderElement).join(''); + } + + return ''; +} + +export function jsxToString(element: JSX.Element): string { + console.log(element); + return renderElement(element); +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index c1ac7ad..0bf31e1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -34,7 +34,7 @@ export default async function Test() {
{articles.map((article, i) => { // Return the element. Also pass key - return () + return () })}
diff --git a/src/components/client/admin/PostEditor.tsx b/src/components/client/admin/PostEditor.tsx index e95087c..e1791ac 100644 --- a/src/components/client/admin/PostEditor.tsx +++ b/src/components/client/admin/PostEditor.tsx @@ -1,18 +1,26 @@ import { ActionResult } from "@/app/lib/actions/ActionResult"; import { handleActionResult } from "@/app/lib/actions/clientActionHandler"; -import { Post } from "@/model/Post"; -import { Project } from "@/model/Project"; +import { Post, Project, Bucket, PostBucket, Attachment } from "@/model/Models"; import { Attributes } from "@sequelize/core"; +import { UUID } from "crypto"; import { ChangeEventHandler, MouseEventHandler, useLayoutEffect, useRef, useState } from "react"; +import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from "react-bootstrap"; export type PostTableCallbacks = { savePost: (p:Partial>)=>any; closeEditor: ()=>any; } +type PostEditorBucket = Partial> & { + attachments:Partial>[] +} + +type PostEditorPost = Partial> & { + buckets:PostEditorBucket[] +} export type EditorProps = { open:boolean; - post:Partial>; + post:PostEditorPost; projects?:Attributes[]; callbacks:PostTableCallbacks; } @@ -23,7 +31,7 @@ export default function PostEditor(props:EditorProps){ let [title,setTitle] = useState(props.post?.title) let [projectID,setProjectID] = useState(props.post?.project_id) let textbox:any = useRef(undefined); - + function adjustHeight() { if(!textbox.current || !textbox.current.style) return textbox.current.style.height = "fit-content"; @@ -55,6 +63,31 @@ export default function PostEditor(props:EditorProps){ {props.projects?.map(p=>)} +

Attachments

+ + + + + + { + (()=>{ + let bucketMap:Map = new Map(props.post.buckets.map((b)=>[b.id as UUID,b])); + let bucketList = [...props.post.buckets.map((b)=>b.id)]; + return bucketList.map((e)=>{ + return {e}
    {bucketMap.get(e as UUID)?.attachments.map((attachment)=>
  • {attachment.filename}
  • )}
+ }) + })() + } + + + + ... + +
Buckets
+ + ... +
+
diff --git a/src/components/server/admin/views/PostView.tsx b/src/components/server/admin/views/PostView.tsx index 6ad50ab..7dab099 100644 --- a/src/components/server/admin/views/PostView.tsx +++ b/src/components/server/admin/views/PostView.tsx @@ -1,14 +1,13 @@ cache: 'no-store' import { tryFetchPosts } from "@/app/api/post/route"; -import { Post } from "@/model/Post"; import { constructAPIUrl } from "@/util/Utils"; import { ReactNode, useEffect } from "react"; import TableGen from "../../../client/TableGen"; import PostTable from "@/components/client/admin/PostTable"; import { deletePost, getPosts, updatePost } from "@/app/lib/actions/entityManagement/postActions"; import { getProjects } from "@/app/lib/actions/entityManagement/projectActions"; -import { Project } from "@/model/Project"; +import { Bucket, Project, Post, dbSync, Attachment } from "@/model/Models"; type Props = { children?:ReactNode @@ -16,6 +15,8 @@ type Props = { export default async function PostView(props:Props){ + await dbSync; + const headings = [ '#', 'Title', @@ -32,7 +33,8 @@ export default async function PostView(props:Props){ getProjects, savePost:updatePost }; - const posts:Post[] = await Post.findAll().then(posts=>posts.map((e)=>JSON.parse(JSON.stringify(e)))); + + const posts:Post[] = await Post.findAll({include: {model: Bucket, include: {model: Attachment}}}).then(posts=>posts.map((e)=>JSON.parse(JSON.stringify(e)))); const projects = await Project.findAll().then(projects=>projects.map((e)=>JSON.parse(JSON.stringify(e)))); return ( diff --git a/src/components/shared/brand/header.module.css b/src/components/shared/brand/header.module.css index e09d6ef..188e085 100644 --- a/src/components/shared/brand/header.module.css +++ b/src/components/shared/brand/header.module.css @@ -1,6 +1,6 @@ .header{ display: flex; - background-color: #008BC7; + /* background-color: #008BC7; */ flex-direction: row; flex-grow: 0; } diff --git a/src/components/shared/brand/header.tsx b/src/components/shared/brand/header.tsx index f9ed826..a930d8c 100644 --- a/src/components/shared/brand/header.tsx +++ b/src/components/shared/brand/header.tsx @@ -1,10 +1,10 @@ import styles from "./header.module.css" export default function Header() { - return
+ return
-
+
Andreas
Schaafsma
>Software Developer
diff --git a/src/components/shared/navigation/navbar.module.css b/src/components/shared/navigation/navbar.module.css index 30a9017..25729dd 100644 --- a/src/components/shared/navigation/navbar.module.css +++ b/src/components/shared/navigation/navbar.module.css @@ -1,5 +1,5 @@ .navbar{ - background-color: #136D94; + /* background-color: #136D94; */ flex-grow: 0; } .navItem{ diff --git a/src/components/shared/navigation/navbar.tsx b/src/components/shared/navigation/navbar.tsx index 1454d88..54cbedf 100644 --- a/src/components/shared/navigation/navbar.tsx +++ b/src/components/shared/navigation/navbar.tsx @@ -5,7 +5,7 @@ const navList = navItems.map(value => export default function Navbar() { - return