From 86eb422dd50c8b5a3b468fbf7d9648586310a71c Mon Sep 17 00:00:00 2001 From: Andreas Schaafsma Date: Tue, 11 Jun 2024 02:23:22 +0200 Subject: [PATCH] implemented file upload for attachments --- src/app/admin/[[...slug]]/page.tsx | 8 +- src/app/admin/layout.tsx | 3 +- src/app/api/attachment/route.ts | 59 ++++++--- src/app/examplepage/example.mdx | 5 + src/app/examplepage/page.tsx | 25 ++++ src/app/lib/actions/ActionResult.ts | 5 + src/app/lib/{ => actions}/actions.ts | 11 +- src/app/lib/actions/clientActionHandler.ts | 11 ++ .../actions/entityManagement/postActions.ts | 33 +++++ .../entityManagement/projectActions.ts | 11 ++ src/app/lib/postActions.ts | 24 ---- src/app/lib/projectActions.ts | 7 -- src/components/client/admin/PostEditor.tsx | 63 ++++++++++ src/components/client/admin/PostTable.tsx | 113 ++++++++---------- src/components/client/admin/loginForm.tsx | 2 +- src/components/server/admin/authHandler.tsx | 2 +- .../server/admin/views/PostView.tsx | 6 +- src/components/server/admin/views/sidebar.tsx | 6 +- src/components/shared/news/article.tsx | 31 ++++- src/mdx-components.tsx | 7 ++ src/model/Attachment.ts | 31 +++++ src/model/Post.ts | 5 + src/providers/providers.tsx | 13 +- 23 files changed, 333 insertions(+), 148 deletions(-) create mode 100644 src/app/examplepage/example.mdx create mode 100644 src/app/examplepage/page.tsx create mode 100644 src/app/lib/actions/ActionResult.ts rename src/app/lib/{ => actions}/actions.ts (91%) create mode 100644 src/app/lib/actions/clientActionHandler.ts create mode 100644 src/app/lib/actions/entityManagement/postActions.ts create mode 100644 src/app/lib/actions/entityManagement/projectActions.ts delete mode 100644 src/app/lib/postActions.ts delete mode 100644 src/app/lib/projectActions.ts create mode 100644 src/components/client/admin/PostEditor.tsx create mode 100644 src/mdx-components.tsx create mode 100644 src/model/Attachment.ts diff --git a/src/app/admin/[[...slug]]/page.tsx b/src/app/admin/[[...slug]]/page.tsx index f569448..2fe049c 100644 --- a/src/app/admin/[[...slug]]/page.tsx +++ b/src/app/admin/[[...slug]]/page.tsx @@ -1,7 +1,7 @@ 'use server' -import { getCookieAuth } from "@/app/lib/actions"; +import { getCookieAuth } from "@/app/lib/actions/actions"; import AuthHandler from "@/components/server/admin/authHandler"; -import Sidebar from "@/components/server/admin/views/sidebar"; +import Sidebar, { SidebarEntry } from "@/components/server/admin/views/sidebar"; import { cookies } from "next/headers"; import PostView from "@/components/server/admin/views/PostView"; import { ReactNode } from "react"; @@ -12,10 +12,6 @@ type Props = { }; } -export type SidebarEntry = { - label:string; - view:string; -} function Home(){ diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index 2e48615..01fb0c8 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from 'next/font/google' import StyledJsxRegistry from '../registry'; import Providers from '@/providers/providers'; import 'bootstrap/dist/css/bootstrap.css'; +import { Toaster } from 'react-hot-toast'; const inter = Inter({ subsets: ['latin'], fallback: ['system-ui', 'arial'] }) @@ -18,7 +19,7 @@ export default function RootLayout({ }) { return ( - {children} + {children} ) } diff --git a/src/app/api/attachment/route.ts b/src/app/api/attachment/route.ts index 8041f9e..ba025f5 100644 --- a/src/app/api/attachment/route.ts +++ b/src/app/api/attachment/route.ts @@ -3,18 +3,20 @@ import { APIError, attemptAPIAction } from "@/util/api/error"; import { Auth, Post, PostTag, Tag, User } from "@/model/Models"; import { cookies } from "next/headers"; +import { Attachment } from "@/model/Attachment"; +import { randomUUID } from "crypto"; +import { mkdir, mkdirSync, writeFile } from "fs"; async function tryCreateAttachment(request: Request) { // Make sure the DB is ready - await PostTag.sync(); - await Tag.sync(); + await Attachment.sync(); await Post.sync(); // Prepare data - const requestBody = await request.json(); + const formData = await request.formData(); const authCkie = await cookies().get("auth"); // Sanity check auth cookie @@ -41,26 +43,45 @@ async function tryCreateAttachment(request: Request) { if (!auth || !auth.user) throw new APIError({ status: 401, responseText: "Authentication Error" }); // Handle incomplete data or other problems - 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 (!formData) throw new APIError({ status: 500, responseText: "Empty request body" }); + const files:any[] = formData.getAll('files') + + if (!files) throw new APIError({ status: 500, responseText: "Missing file" }); 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)}` }); - // Create a new Post in the database - const post = await Post.create( - { - content: requestBody.content, - user_id: auth.user.id, - title: requestBody.title, - },{ - include: { - association: Post.associations.user - } - }).then(post=>post.reload()) + // peepee + const uuid = randomUUID() + const fileArray:{name:string, content:Promise>|ReadableStreamReadResult}[] = await Promise.all( files.map((file:File) => { + return {'name':( ()=>file.name)(), 'content':file.stream().getReader().read()}; + })); + + + + let finalFileArray:{name:string,content:ReadableStreamReadResult}[] = await (async () => { + for(let file in fileArray){ + fileArray[file].content = await fileArray[file].content + } + return [...fileArray as {name:string,content:ReadableStreamReadResult}[]] + })() ; + + mkdirSync(`./bucket/${uuid}/`) + for(let file in finalFileArray){ + + writeFile(`./bucket/${uuid}/${finalFileArray[file].name}`,Buffer.from(finalFileArray[file].content.value as Uint8Array),(e)=>{console.log(e)}) + } + // const kanker = files.map(parseFiles) + + + + // console.log(await kanker[0]); + + + return new Response(JSON.stringify({ + 'files': fileArray, + 'uuid': uuid, + }), { status: 200 }); - // Return the response - return new Response(JSON.stringify(post), { status: 200 }); } diff --git a/src/app/examplepage/example.mdx b/src/app/examplepage/example.mdx new file mode 100644 index 0000000..7b9d12f --- /dev/null +++ b/src/app/examplepage/example.mdx @@ -0,0 +1,5 @@ +export function Thing() { + return <>World +} + +# Hello \ No newline at end of file diff --git a/src/app/examplepage/page.tsx b/src/app/examplepage/page.tsx new file mode 100644 index 0000000..88f6c81 --- /dev/null +++ b/src/app/examplepage/page.tsx @@ -0,0 +1,25 @@ +import type { GetStaticProps } from 'next' +import { MDXRemote } from 'next-mdx-remote/rsc' +import ExampleComponent from './example.mdx' + + +const components = { ExampleComponent } + +export default async function ExamplePage() { + return ( +
+ +
+ ) +} + +// export const getStaticProps: GetStaticProps<{ +// mdxSource: MDXRemoteSerializeResult +// }> = async () => { +// const mdxSource = await serialize('some *mdx* content: ') +// return { props: { mdxSource } }} \ No newline at end of file diff --git a/src/app/lib/actions/ActionResult.ts b/src/app/lib/actions/ActionResult.ts new file mode 100644 index 0000000..a9046ab --- /dev/null +++ b/src/app/lib/actions/ActionResult.ts @@ -0,0 +1,5 @@ + +export type ActionResult = { + error?:string; + result?:T; + } \ No newline at end of file diff --git a/src/app/lib/actions.ts b/src/app/lib/actions/actions.ts similarity index 91% rename from src/app/lib/actions.ts rename to src/app/lib/actions/actions.ts index 8ffb080..fd32f6c 100644 --- a/src/app/lib/actions.ts +++ b/src/app/lib/actions/actions.ts @@ -9,6 +9,7 @@ import { Attribute, Attributes } from "@sequelize/core"; import { User } from "@/model/User"; import { AuthProps } from "@/providers/providers"; import { Auth } from "@/model/Auth"; +import { ActionResult } from "./ActionResult"; type LoginReturn = { cookie?:unknown, @@ -80,10 +81,10 @@ export async function serverValidateSessionCookie(koek:string):Promise return false } -export async function userIsAdmin(user:Partial>):Promise +export async function userIsAdmin():Promise { const cookieAuthValue = await cookies().get('auth')?.value; - const cookieAuthSanitized = decodeURIComponent(cookieAuthValue ? cookieAuthValue: ""); + const cookieAuthSanitized = cookieAuthValue? JSON.parse(JSON.stringify(cookieAuthValue)) : ""; if(!cookieAuthSanitized) return false; const parsedAuth = JSON.parse(cookieAuthSanitized); @@ -107,8 +108,9 @@ export async function userIsAdmin(user:Partial>):Promise { const cookieAuthValue = await cookies().get('auth')?.value; - const cookieAuthSanitized = decodeURIComponent(cookieAuthValue ? cookieAuthValue: ""); - + const cookieAuthSanitized = cookieAuthValue? JSON.parse(JSON.stringify(cookieAuthValue)) : ""; + console.log("kanker koek") + if(!cookieAuthSanitized) return {} const kd = JSON.parse(cookieAuthSanitized); @@ -126,4 +128,3 @@ export async function getCookieAuth():Promise } return authObject; } - diff --git a/src/app/lib/actions/clientActionHandler.ts b/src/app/lib/actions/clientActionHandler.ts new file mode 100644 index 0000000..f9fed42 --- /dev/null +++ b/src/app/lib/actions/clientActionHandler.ts @@ -0,0 +1,11 @@ +'use client' + +import toast from "react-hot-toast"; +import { ActionResult } from "./ActionResult"; + + +export function handleActionResult(actionResult:ActionResult): T | undefined +{ + if(actionResult.error) toast.error(actionResult.error,{position:"top-center",duration:10000}) + else return actionResult.result; +} \ No newline at end of file diff --git a/src/app/lib/actions/entityManagement/postActions.ts b/src/app/lib/actions/entityManagement/postActions.ts new file mode 100644 index 0000000..3b1b2fc --- /dev/null +++ b/src/app/lib/actions/entityManagement/postActions.ts @@ -0,0 +1,33 @@ +'use server'; + +import { revalidatePath, revalidateTag } from 'next/cache' +import { Post } from "@/model/Post"; +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'; + + + + + +export async function deletePost(postID: number): Promise> { + // revalidatePath("/admin/man-post","page") + if(! await userIsAdmin()) return {error:"Unauthorized, not deleting Post", result: false} + const destroy = await Post.destroy({ where: { id: postID } }); + return {result: true}; +} + + +export async function getPosts(): Promise[]>> { + if(! await userIsAdmin()) return {error:"Unauthorized, not fetching Posts."} + const posts = await Post.findAll(); + return {result:JSON.parse(JSON.stringify(posts))}; +} + +export async function updatePost(postAttributes: Partial>): Promise[]>> { + 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))}; +} \ No newline at end of file diff --git a/src/app/lib/actions/entityManagement/projectActions.ts b/src/app/lib/actions/entityManagement/projectActions.ts new file mode 100644 index 0000000..83bc998 --- /dev/null +++ b/src/app/lib/actions/entityManagement/projectActions.ts @@ -0,0 +1,11 @@ +'use server'; +import { Project } from "@/model/Project"; +import { ActionResult } from "../ActionResult"; +import { Attributes } from "@sequelize/core"; +import { userIsAdmin } from "../actions"; + +export async function getProjects(): Promise[]>> { + if (! await userIsAdmin()) return { error: 'Unauthorized, not fetching Projects' } + const posts = await Project.findAll(); + return { result: JSON.parse(JSON.stringify(posts)) }; +} diff --git a/src/app/lib/postActions.ts b/src/app/lib/postActions.ts deleted file mode 100644 index 696a2d9..0000000 --- a/src/app/lib/postActions.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server'; - -import { revalidatePath, revalidateTag } from 'next/cache' -import { Post } from "@/model/Post"; -import { Attributes, where } from "@sequelize/core"; - - -export async function deletePost(postID: number): Promise { - revalidatePath("/admin/man-post","page") - const destroy = await Post.destroy({ where: { id: postID } }); - return true; -} - - -export async function getPosts(): Promise { - const posts = await Post.findAll(); - return JSON.stringify(posts); -} - -export async function updatePost(postAttributes: Partial>): Promise { - revalidatePath("/admin/man-post","page") - const post = await Post.update(postAttributes, {where:{id:postAttributes.id}}); - return JSON.stringify(post); -} \ No newline at end of file diff --git a/src/app/lib/projectActions.ts b/src/app/lib/projectActions.ts deleted file mode 100644 index 9a55808..0000000 --- a/src/app/lib/projectActions.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use server'; -import { Project } from "@/model/Project"; - -export async function getProjects(): Promise { - const posts = await Project.findAll(); - return JSON.stringify(posts); -} diff --git a/src/components/client/admin/PostEditor.tsx b/src/components/client/admin/PostEditor.tsx new file mode 100644 index 0000000..e95087c --- /dev/null +++ b/src/components/client/admin/PostEditor.tsx @@ -0,0 +1,63 @@ +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 { Attributes } from "@sequelize/core"; +import { ChangeEventHandler, MouseEventHandler, useLayoutEffect, useRef, useState } from "react"; + +export type PostTableCallbacks = { + savePost: (p:Partial>)=>any; + closeEditor: ()=>any; +} + +export type EditorProps = { + open:boolean; + post:Partial>; + projects?:Attributes[]; + callbacks:PostTableCallbacks; +} + + +export default function PostEditor(props:EditorProps){ + let [content,setContent] = useState(props.post?.content) + 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"; + textbox.current.style.height = `${textbox.current.scrollHeight}px`; + } + + useLayoutEffect(adjustHeight, []); + + const onTextAreaChange:ChangeEventHandler = (e) => {setContent(e.target.value);adjustHeight()}; + const projectSelectionChange:ChangeEventHandler = (e)=>setProjectID(parseInt(e.target.value)); + const onClickSaveButton:MouseEventHandler = (e)=>{ + props.callbacks.savePost({ + id: props.post.id as number, + content:content as string, + title:title as string, + }); + } + const onClickCancelButton:MouseEventHandler = (e)=>{props.callbacks.closeEditor();} + + return <> +
+

Edit Post

+

Title

+ setTitle(e.target.value)} type='text' className="m-2"> +

Content

+ +

Project

+ + + + +
+ +} \ No newline at end of file diff --git a/src/components/client/admin/PostTable.tsx b/src/components/client/admin/PostTable.tsx index f5ccd0e..d2124f5 100644 --- a/src/components/client/admin/PostTable.tsx +++ b/src/components/client/admin/PostTable.tsx @@ -1,18 +1,21 @@ 'use client' -import React, { ChangeEvent, LegacyRef, MutableRefObject, ReactNode, useLayoutEffect, useRef } from "react" +import React, { ChangeEvent, ChangeEventHandler, LegacyRef, MouseEventHandler, MutableRefObject, ReactNode, useLayoutEffect, useRef } from "react" import TableGen from "../TableGen" import { Attributes, CreationAttributes } from "@sequelize/core"; import { Post } from "@/model/Post"; import { useState } from "react"; import { Project } from "@/model/Project"; -import { revalidatePath } from "next/cache"; -import { usePathname } from 'next/navigation'; +import toast from "react-hot-toast" +import { ActionResult } from "@/app/lib/actions/ActionResult"; +import { handleActionResult } from "@/app/lib/actions/clientActionHandler"; +import PostEditor, { EditorProps } from "./PostEditor"; +import { getPosts } from "@/app/lib/actions/entityManagement/postActions"; type Actions = { - deletePost:any - getPosts:()=>Promise - getProjects:()=>Promise - savePost:(data:Partial>)=>Promise + deletePost:(id:number)=>Promise> + getPosts:()=>Promise[]>> + getProjects:()=>Promise[]>> + savePost:(data:Partial>)=>Promise[]>> } type Props = { @@ -23,61 +26,12 @@ type Props = { actions:Actions; } -type EditorProps = { - open:boolean; - post:Partial>; - projects?:Attributes[] - actionSavePost:(data:Partial>)=>Promise -} export default function PostTable(props:Props){ - const pathname = usePathname(); - function RenderEditor(props:EditorProps){ - let [content,setContent] = useState(props.post?.content) - 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"; - textbox.current.style.height = `${textbox.current.scrollHeight}px`; - } - - useLayoutEffect(adjustHeight, []); - - return
-

Edit Post

-

Title

- setTitle(e.target.value)} type='text' className="m-2"> -

Content

- -

Project

- - - - -
- } - function closeEditor(){ setEditor({ open:false @@ -87,33 +41,60 @@ export default function PostTable(props:Props){ function showEditor(entry:Attributes){ setEditor({ open: true, - post: entry, - actionSavePost: props.actions.savePost + post: entry }) } const initEditorState:Partial = { open: false, - post: {}, - actionSavePost: props.actions.savePost + post: {} } + const [posts, setPosts] = useState(props.data); const [editor, setEditor] = useState(initEditorState) const [projects, setProjects] = useState(props.projects); + function deletePost(entry:Attributes){ - props.actions?.deletePost(entry.id); - props.actions?.getPosts().then((p)=>setPosts(JSON.parse(p))); + props.actions?.deletePost(entry.id) + .then(actionResult=>{ + const result = handleActionResult(actionResult); + if (! result) return; + posts.splice(posts.indexOf(entry),1); + setPosts([...posts]) + toast.success('Removed Post:' +entry.id); + }); + } const aifa = (a:ReactNode,b:ReactNode) => a ? a : b function refetch(){ - props.actions.getPosts().then(e=>setPosts(JSON.parse(e))) - props.actions.getProjects().then(e=>setProjects(JSON.parse(e))) - console.log(pathname); + props.actions?.getPosts().then((p)=>{ + const result = handleActionResult(p) + if (result) setPosts(result) + }); + props.actions.getProjects().then(e=>{ + const result = handleActionResult(e); + if (result) setProjects(result) + }) } + function savePost(e:Partial>){ + props.actions.savePost({ + id:e.id, + content: e.content, + title: e.title, + project_id: e.project_id + }) + .then(res=>handleActionResult(res)) + .then(getPosts) + .then(res=>{ + if (!res.error) setPosts(res.result as Attributes[]); + }) + closeEditor(); + }; + return <> {posts.map((d:Attributes)=>{ @@ -132,7 +113,7 @@ export default function PostTable(props:Props){ {(editor.open && editor.post && editor.post.id == d.id)? - :""} + :""} })} diff --git a/src/components/client/admin/loginForm.tsx b/src/components/client/admin/loginForm.tsx index 39c05be..0b34a4c 100644 --- a/src/components/client/admin/loginForm.tsx +++ b/src/components/client/admin/loginForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { serverAttemptAuthenticateUser } from "@/app/lib/actions"; +import { serverAttemptAuthenticateUser } from "@/app/lib/actions/actions"; import { AuthContext } from "@/providers/providers"; import { constructAPIUrl } from "@/util/Utils"; import { createContext, useState } from "react"; diff --git a/src/components/server/admin/authHandler.tsx b/src/components/server/admin/authHandler.tsx index d971930..0d01bae 100644 --- a/src/components/server/admin/authHandler.tsx +++ b/src/components/server/admin/authHandler.tsx @@ -5,7 +5,7 @@ import LoginForm from "@/components/client/admin/loginForm"; import AdminPanel from "@/components/server/admin/adminPanel"; import ClientAuthHandler from "@/components/client/admin/clientAuthHandler"; -import { serverValidateSessionCookie } from "@/app/lib/actions"; +import { serverValidateSessionCookie } from "@/app/lib/actions/actions"; import { constructAPIUrl } from "@/util/Utils"; import { ReactNode } from "react"; import { AuthContext, AuthProps } from "@/providers/providers"; diff --git a/src/components/server/admin/views/PostView.tsx b/src/components/server/admin/views/PostView.tsx index 6fe762b..6ad50ab 100644 --- a/src/components/server/admin/views/PostView.tsx +++ b/src/components/server/admin/views/PostView.tsx @@ -1,3 +1,4 @@ +cache: 'no-store' import { tryFetchPosts } from "@/app/api/post/route"; import { Post } from "@/model/Post"; @@ -5,8 +6,8 @@ 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/postActions"; -import { getProjects } from "@/app/lib/projectActions"; +import { deletePost, getPosts, updatePost } from "@/app/lib/actions/entityManagement/postActions"; +import { getProjects } from "@/app/lib/actions/entityManagement/projectActions"; import { Project } from "@/model/Project"; type Props = { @@ -33,6 +34,7 @@ export default async function PostView(props:Props){ }; const posts:Post[] = await Post.findAll().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 (

Post Management

diff --git a/src/components/server/admin/views/sidebar.tsx b/src/components/server/admin/views/sidebar.tsx index 64f5956..a632bad 100644 --- a/src/components/server/admin/views/sidebar.tsx +++ b/src/components/server/admin/views/sidebar.tsx @@ -1,13 +1,15 @@ import './sidebar.css' import { Button, NavLink } from 'react-bootstrap'; -import { SidebarEntry } from '@/app/admin/page'; import React, { ReactNode, useState } from 'react'; import Link from 'next/link'; import { headers } from 'next/headers'; - +export type SidebarEntry = { + label:string; + view:string; + } type Props = { children?:ReactNode; diff --git a/src/components/shared/news/article.tsx b/src/components/shared/news/article.tsx index 829a832..dc31b0d 100644 --- a/src/components/shared/news/article.tsx +++ b/src/components/shared/news/article.tsx @@ -1,17 +1,42 @@ + import Tagbar from "@/components/shared/news/tagbar"; import "/public/global.css" import "@/app/index.css" import styles from "./article.module.css" +import { serialize } from 'next-mdx-remote/serialize' +import { MDXComponents, MDXContent } from "mdx/types"; +import { MDXRemote } from 'next-mdx-remote/rsc' -export default function Article(params: { id: string|undefined, title: string|undefined, content: string|undefined, date?:string|undefined } ) { +export async function ExampleComponent(){ + return ( +
aaa
+ ) +} + +export default async function Article(params: { id: string|undefined, title: string|undefined, content: string|undefined, date?:string|undefined } ) { + + + const components = { ExampleComponent } + return (

{params.title}

-

{params.content}

+
+ +
{params.date}

); -} \ No newline at end of file +} + +// export const getStaticProps: GetStaticProps<{ +// mdxSource: MDXRemoteSerializeResult +// }> = async () => { +// const mdxSource = await serialize('some *mdx* content: ') +// return { props: { mdxSource } } +// } diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx new file mode 100644 index 0000000..cf1adda --- /dev/null +++ b/src/mdx-components.tsx @@ -0,0 +1,7 @@ +import type { MDXComponents } from 'mdx/types' + +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { + ...components, + } +} \ No newline at end of file diff --git a/src/model/Attachment.ts b/src/model/Attachment.ts new file mode 100644 index 0000000..79627a8 --- /dev/null +++ b/src/model/Attachment.ts @@ -0,0 +1,31 @@ +import { Association, BelongsToGetAssociationMixin, BelongsToManyGetAssociationsMixin, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, NonAttribute, Sequelize } from "@sequelize/core"; +import { Post } from "./Post"; + +import { SqliteDialect } from '@sequelize/sqlite3'; +import { Attribute, AutoIncrement, BelongsTo, BelongsToMany, NotNull, PrimaryKey, Unique } from "@sequelize/core/decorators-legacy"; + +export class Attachment extends Model, InferCreationAttributes> { + @PrimaryKey + @AutoIncrement + @Attribute(DataTypes.INTEGER) + @Unique + declare id: number; + @Attribute(DataTypes.STRING) + declare path: string + + // Associations + @Attribute(DataTypes.INTEGER) + @NotNull + declare postid: number; + @BelongsTo(()=>Post,{foreignKey: 'postid', inverse: {type: "hasMany", as: 'attachments'}}) + declare post?:NonAttribute; + declare static associations: { + posts: Association; + }; +} + +const sequelize = new Sequelize({ + dialect: SqliteDialect, + storage: 'db.sqlite', + models: [Attachment] +}) diff --git a/src/model/Post.ts b/src/model/Post.ts index 7d3032c..7caed96 100644 --- a/src/model/Post.ts +++ b/src/model/Post.ts @@ -5,6 +5,7 @@ import { Attribute, AutoIncrement, BelongsTo, BelongsToMany, CreatedAt, Default, import { Tag } from "./Tag"; import { PostTag } from "./PostTag"; import { Project } from "./Project"; +import { Attachment } from "./Attachment"; export class Post extends Model, InferCreationAttributes> { @@ -45,6 +46,10 @@ export class Post extends Model, InferCreationAttributes

Tag, { through: { model: ()=>PostTag, unique: false}, inverse: {as: 'taggedPosts'} }) declare postTags?:NonAttribute; + + /** Defined by {@link Attachment.post} */ + declare Attachments:NonAttribute[]; + declare getUser: BelongsToGetAssociationMixin; diff --git a/src/providers/providers.tsx b/src/providers/providers.tsx index 673f8ff..c64d301 100644 --- a/src/providers/providers.tsx +++ b/src/providers/providers.tsx @@ -10,20 +10,11 @@ export type AuthProps = { auth?: Attributes user?: Attributes } + let p: AuthProps = {} -export type AdminViewProps = { - view: string; -} - -let avp: AdminViewProps = { - view: "home", -} - -export const AdminViewContext = createContext(avp); export const AuthContext = createContext(p); - interface Props { children?: ReactNode; params?: any; @@ -31,6 +22,6 @@ interface Props { export default function Providers(props:Props){ return ( - {props.children} + {props.children} ) } \ No newline at end of file