various changes

This commit is contained in:
Andreas Schaafsma 2024-06-17 15:28:57 +02:00 committed by Andreas
parent c3a8469371
commit adc02f2e5b
19 changed files with 268 additions and 155 deletions

View File

@ -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,
},{

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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<ActionResult<boolean>> {
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<ActionResult<boolean>>
export async function getPosts(): Promise<ActionResult<Attributes<Post>[]>> {
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<Attributes<Post>>): Promise<ActionResult<Attributes<Post>[]>> {
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))};

View File

@ -0,0 +1,39 @@
import React, { ReactElement, ReactNode } from 'react';
function renderAttributes(props: Record<string, any>): 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}</${type}>`;
}
if (Array.isArray(element)) {
return element.map(renderElement).join('');
}
return '';
}
export function jsxToString(element: JSX.Element): string {
console.log(element);
return renderElement(element);
}

View File

@ -34,7 +34,7 @@ export default async function Test() {
<main>
{articles.map((article, i) => {
// Return the element. Also pass key
return (<ArticlePreview key={article?.id} id={article?.id?.toString()} content={article?.content} title={article?.title}></ArticlePreview>)
return (<ArticlePreview key={article?.id} post={article}></ArticlePreview>)
})}
</main>
</PageContainer>

View File

@ -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<Attributes<Post>>)=>any;
closeEditor: ()=>any;
}
type PostEditorBucket = Partial<Attributes<Bucket>> & {
attachments:Partial<Attributes<Attachment>>[]
}
type PostEditorPost = Partial<Attributes<Post>> & {
buckets:PostEditorBucket[]
}
export type EditorProps = {
open:boolean;
post:Partial<Attributes<Post>>;
post:PostEditorPost;
projects?:Attributes<Project>[];
callbacks:PostTableCallbacks;
}
@ -55,6 +63,31 @@ export default function PostEditor(props:EditorProps){
<option value={0}>unassigned</option>
{props.projects?.map(p=><option value={p.id}>{p.readableIdentifier}</option>)}
</select>
<h2>Attachments</h2>
<table className="table table-striped">
<thead>
<tr><td>Buckets</td></tr>
</thead>
<tbody>
{
(()=>{
let bucketMap:Map<UUID,PostEditorBucket> = 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 <tr><Accordion><AccordionItem eventKey={bucketList.indexOf(e).toString()}><AccordionHeader>{e}</AccordionHeader><AccordionBody><ul>{bucketMap.get(e as UUID)?.attachments.map((attachment)=><li>{attachment.filename}</li>)}</ul></AccordionBody></AccordionItem></Accordion></tr>
})
})()
}
<tr>
<td colSpan={4}>
<table className="table mb-0">
...
</table>
</td>
</tr>
...
</tbody>
</table>
<button type="button" className="m-2 btn btn-primary">Preview</button>
<button type="button" className="m-2 btn btn-success" onClick={onClickSaveButton}>Save</button>
<button type="button" className="m-2 btn btn-danger" onClick={onClickCancelButton}>Cancel</button>

View File

@ -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 (

View File

@ -1,6 +1,6 @@
.header{
display: flex;
background-color: #008BC7;
/* background-color: #008BC7; */
flex-direction: row;
flex-grow: 0;
}

View File

@ -1,10 +1,10 @@
import styles from "./header.module.css"
export default function Header() {
return <div className={`${styles.header} pp`}>
return <div className={`${styles.header} pp bg-primary`}>
<img src="/logo.png" width="80px" height="auto" alt="" />
<div className={styles.headertitle}>
<div className={`${styles.headertitle}`}>
<div style={{flexGrow:1}}>Andreas<br/>Schaafsma</div>
<div>&gt;Software Developer</div>
</div>

View File

@ -1,5 +1,5 @@
.navbar{
background-color: #136D94;
/* background-color: #136D94; */
flex-grow: 0;
}
.navItem{

View File

@ -5,7 +5,7 @@ const navList = navItems.map(value => <a key={value} className={styles.navItem}>
export default function Navbar() {
return <nav className={`${styles.navbar}`}>
return <nav className={`${styles.navbar} bg-secondary`}>
<ul>{navList}</ul>
</nav>
}

View File

@ -2,25 +2,50 @@ import Tagbar from "@/components/shared/news/tagbar";
import styles from "@/components/shared/news/article-preview.module.css"
import bg from "public/placeholder-square.png"
import { ReactNode } from "react"
import { DOMElement, JSXElementConstructor, ReactNode } from "react"
import { Style } from "util";
import Link from "next/link";
import { redirect } from 'next/navigation';
import { Router } from "next/router";
import { useRouter } from 'next/navigation'
import { truncateString } from "@/util/Utils";
import { MDXRemote } from "next-mdx-remote/rsc";
import { ExampleComponent } from "./article";
import ReactDOM from "react-dom";
import renderToString from 'react-dom/server'
import { jsxToString } from "@/app/lib/jsxtostring";
import remarkMdx from "remark-mdx";
import { remark } from "remark";
import strip from 'remark-mdx-to-plain-text';
import { Post } from "@/model/Post";
import { Attributes } from "@sequelize/core";
type ArticlePreviewPost = Attributes<Post>
type Props = {
id?:string
title?:string
content?:string
date?:string
post:ArticlePreviewPost;
}
export default async function ArticlePreview(props:Props){
const components = { ExampleComponent }
const content = ["import ExampleComponent from './article';\n",props.post.content].join('\n');
let stripped = ''
remark()
.use(remarkMdx)
.use(strip)
.process(content, (err:any, file:any):any => {
if (err) throw err
// console.log(String(file));
stripped=String(file)
return String(file)
} )
console.log(stripped)
export default function ArticlePreview(props:Props){
// const parsedMDX = await MDXRemote({source:props.content,components:components});
// const b = new ReactDOM.contain
// const renderedMDX = ReactDOM.render(parsedMDX,)
// console.log(eval(parsedMDX as any))
// const a = jsxToString(renderedMDX);
// console.log(parsedMDX);
// if (!props.content)
return (
<section className={styles.previewbox}>
@ -28,8 +53,8 @@ export default function ArticlePreview(props:Props){
<div className={`${styles.summary} flex flex-col justify-between p-0`}>
<section className="w-[100%]">
<span className="inline-block"><h2><Link href={`/article/${props.id}`}>{props.title}</Link></h2></span>
<p>{truncateString(props.content,255)}</p>
<span className="inline-block"><h2><Link href={`/article/${props.post.id}`}>{props.post.title}</Link></h2></span>
<p>{truncateString(props.post.description? props.post.description : "No Description",255)}</p>
</section>
<Tagbar/>
</div>

View File

@ -4,7 +4,6 @@ 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'
@ -20,14 +19,15 @@ export default async function Article(params: { id: string|undefined, title: str
const components = { ExampleComponent }
return (
<article id={`post-${params.id}`}>
<h1 className=".article-title pl-5 pr-5">{params.title}</h1>
<div className={`${styles.imagecontainer} m-5`}/>
<div className="pl-5 pr-5"><Tagbar/></div>
<div className=".article-content p-5">
<MDXRemote
source={params.content?params.content:""} components={components}
/>
<article id={`post-${params.id}`} className="bg-background-800 w-[80%] m-auto">
<div id="image-container" className={`${styles.imagecontainer} flex flex-col`}>
<h1 id="article-title" className=".article-title p-5 self-start">{params.title}</h1>
<div id="spacer" className="flex flex-grow"></div>
<div id="tagbar" className="p-5 self-start"><Tagbar/></div>
</div>
<div className=".article-content p-5 whitespace-pre">
<MDXRemote source={params.content?params.content:""} components={components}/>
</div>
<section className=".article-date">{params.date}</section> <br/>
</article>

View File

@ -2,7 +2,7 @@
color: #C0F0FF;
display:flex;
flex-direction: row;
background-color: #1E536A;
/* background-color: #1E536A; */
padding:8px;
flex-grow: 1;
}

View File

@ -6,7 +6,7 @@ interface Props {
}
export default function PageContainer(props:Props) {
return <div className={`${styles.pagecontainer}`}>
return <div className={`${styles.pagecontainer} bg-background`}>
{props.children}
</div>;
}

View File

@ -20,7 +20,7 @@ const sequelize = new Sequelize({
});
const dbSync = (async ()=> await sequelize.sync())().then(()=>{
const dbSync = (async ()=> await sequelize.sync({alter:true}))().then(()=>{
addUserScopes();
addUserPermsScopes();
});

View File

@ -20,9 +20,11 @@ export class Post extends Model<InferAttributes<Post>, InferCreationAttributes<P
declare id: CreationOptional<number>;
@Attribute(DataTypes.STRING)
declare title:string
declare title:string;
@Attribute(DataTypes.STRING)
declare content:string
declare content:string;
@Attribute(DataTypes.STRING)
declare description?:CreationOptional<string>;
// Date thingies
@CreatedAt