Refactoring and reworking dbsetup route
This commit is contained in:
parent
c076918d05
commit
665da4be29
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
29
db/seed/posts.json
Normal file
29
db/seed/posts.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "Blog",
|
||||||
|
"readableIdentifier": "blog",
|
||||||
|
"posts": [
|
||||||
|
{
|
||||||
|
"title": "Test Post",
|
||||||
|
"content": "# Hello <ExampleComponent />\nthis is some **test** markdown and we make some edits\n",
|
||||||
|
"description": "A new post to test the blog system",
|
||||||
|
"project_id": 1,
|
||||||
|
"user_id": 1,
|
||||||
|
"buckets": [
|
||||||
|
{
|
||||||
|
"id": "788dfc19-55ba-482c-8124-277702296dfb",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"path": "FB_IMG_1716665756868.jpg"
|
||||||
|
},{
|
||||||
|
"path": "patchnotes.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
db/seed/posts/FB_IMG_1716665756868.jpg
Normal file
BIN
db/seed/posts/FB_IMG_1716665756868.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
BIN
db/seed/posts/patchnotes.png
Normal file
BIN
db/seed/posts/patchnotes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
21
db/seed/users.json
Normal file
21
db/seed/users.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"users": [{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "changeme",
|
||||||
|
"perms": {
|
||||||
|
"isAdmin": true
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
"username": "notadmin",
|
||||||
|
"password": "changeme",
|
||||||
|
"perms": {
|
||||||
|
"isAdmin": false
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
"username": "theodore",
|
||||||
|
"password": "changeme",
|
||||||
|
"perms": {
|
||||||
|
"isAdmin": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
@ -5,38 +5,109 @@ import { cookies } from "next/headers";
|
|||||||
import { APIError} from "@/util/api/error"
|
import { APIError} from "@/util/api/error"
|
||||||
import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user"
|
import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user"
|
||||||
import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms,dbSync,sequelize} from "@/models";
|
import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms,dbSync,sequelize} from "@/models";
|
||||||
import Sequelize, { DataTypes } from "@sequelize/core";
|
import Sequelize, { CreationAttributes, DataTypes } from "@sequelize/core";
|
||||||
import { SqliteColumnsDescription, SqliteDialect, SqliteQueryInterface } from "@sequelize/sqlite3";
|
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';
|
||||||
|
import { DeepPartial } from "@/util/DeepPartial";
|
||||||
|
import { UUID } from "crypto";
|
||||||
|
|
||||||
|
async function seedUsers(qif: SqliteQueryInterface<SqliteDialect>){
|
||||||
|
const fp = path.resolve('./db/seed/users.json');
|
||||||
|
const json: {users: CreationAttributes<User>[]} = JSON.parse(Buffer.from(readFileSync(fp).valueOf()).toString());
|
||||||
|
|
||||||
|
const users = json.users.map(async user=>{
|
||||||
|
user.password = await hashPassword(user.password);
|
||||||
|
return user;
|
||||||
|
})
|
||||||
|
|
||||||
|
const dbUsers = await User.bulkCreate(await Promise.all(users), {include: User.associations.perms})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedPosts(qif: SqliteQueryInterface<SqliteDialect>){
|
||||||
|
const fp = path.resolve('./db/seed/posts.json');
|
||||||
|
const json: {users: CreationAttributes<User>[]} = JSON.parse(Buffer.from(readFileSync(fp).valueOf()).toString());
|
||||||
|
const projects =[
|
||||||
|
{
|
||||||
|
"name": "Blog",
|
||||||
|
"readableIdentifier": "blog",
|
||||||
|
"posts": [
|
||||||
|
{
|
||||||
|
"title": "Test Post",
|
||||||
|
"content": "# Hello <ExampleComponent />\nthis is some **test** markdown and we make some edits\n",
|
||||||
|
"description": "A new post to test the blog system",
|
||||||
|
"project_id": 1,
|
||||||
|
"user_id": 1,
|
||||||
|
"buckets": [
|
||||||
|
{
|
||||||
|
"id": "788dfc19-55ba-482c-8124-277702296dfb",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"filename": "FB_IMG_1716665756868.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
projects.map(project=>{
|
||||||
|
Project.create({name: project.name, readableIdentifier: project.readableIdentifier});
|
||||||
|
project.posts.map(async post=>{
|
||||||
|
const pst = await Post.create({title:post.title, content:post.content, description:post.description, project_id: post.project_id, user_id: post.user_id});
|
||||||
|
post.buckets.map(async bucket=>{
|
||||||
|
pst.createBucket({id:bucket.id as UUID});
|
||||||
|
bucket.attachments.map(attachment=>{
|
||||||
|
Attachment.create({bucket_id:bucket.id as UUID, filename: attachment.filename}).then((a)=>{
|
||||||
|
copyFile(
|
||||||
|
path.resolve('.','db','seed','post',a.filename),
|
||||||
|
path.resolve('.','bucket',bucket.id,a.filename),
|
||||||
|
()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function seedDatabase(queryInterface:SqliteQueryInterface<SqliteDialect>){
|
|
||||||
|
|
||||||
const password = await hashPassword('changeme');
|
|
||||||
const project = await Project.findOne({where: {
|
const project = await Project.findOne({where: {
|
||||||
readableIdentifier: 'blog'
|
readableIdentifier: 'blog'
|
||||||
}}).then(e=> e ? e : Project.create({name:'General Blog',readableIdentifier:'blog'}));
|
}}).then(e=> e ? e : Project.create({name:'General Blog',readableIdentifier:'blog'}));
|
||||||
const user = await User.findOne({where: {
|
}
|
||||||
username: 'admin'
|
|
||||||
}}).then(e=> e ? e : User.create({username: 'admin', password: password, perms: {isAdmin: true}}, {include: User.associations.perms}));
|
|
||||||
await Post.create({
|
|
||||||
title: 'Test Post',
|
|
||||||
content: `
|
|
||||||
# Hello <ExampleComponent />
|
async function seedDatabase(qif:SqliteQueryInterface<SqliteDialect>){
|
||||||
this is some **test** markdown
|
await seedUsers(qif);
|
||||||
`,
|
await seedPosts(qif)
|
||||||
project_id: project.id,
|
|
||||||
user_id: user.id
|
// await Post.create({
|
||||||
})
|
// title: 'Test Post',
|
||||||
await Post.create({
|
// content: `
|
||||||
title: 'Test Post 2',
|
// # Hello <ExampleComponent />
|
||||||
content: `
|
// this is some **test** markdown
|
||||||
# Hello <ExampleComponent />
|
// `,
|
||||||
this is amother post with some **test** markdown
|
// project_id: project.id,
|
||||||
`,
|
// user_id: user.id
|
||||||
project_id: project.id,
|
// })
|
||||||
user_id: user.id
|
// await Post.create({
|
||||||
})
|
// title: 'Test Post 2',
|
||||||
|
// content: `
|
||||||
|
// # Hello <ExampleComponent />
|
||||||
|
// this is amother post with some **test** markdown
|
||||||
|
// `,
|
||||||
|
// project_id: project.id,
|
||||||
|
// user_id: user.id
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function trySetup(request:Request){
|
async function trySetup(request:Request){
|
||||||
|
|||||||
@ -1,8 +1,30 @@
|
|||||||
|
import { Attachment, Bucket, dbSync } from "@/models";
|
||||||
import { open, openSync, readFileSync } from "fs";
|
import { open, openSync, readFileSync } from "fs";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export async function GET(req:NextRequest, { params }: {params:{slug: string}}){
|
export async function GET(req:NextRequest, { params }: {params:{slug: string[]}}){
|
||||||
|
await dbSync;
|
||||||
|
const filename: `${string}${'.'}${string}` = params.slug[params.slug.length-1] as `${string}${'.'}${string}`;
|
||||||
|
const bucket = await Bucket.findOne({
|
||||||
|
where: {
|
||||||
|
id:params.slug[0],
|
||||||
|
},
|
||||||
|
include: [{
|
||||||
|
association: Bucket.associations.attachments,
|
||||||
|
where: {
|
||||||
|
filename: params.slug[params.slug.length-1]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},)
|
||||||
|
console.log(params.slug);
|
||||||
|
if(!bucket || !bucket.attachments || !bucket.attachments[0] || bucket.attachments[0].filename != params.slug[params.slug.length-1]){
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set("Content-Type", "application/json");
|
||||||
|
return new NextResponse(JSON.stringify({
|
||||||
|
error:"Access Denied"
|
||||||
|
}),{headers:headers});
|
||||||
|
}
|
||||||
const fp = path.resolve('.',`bucket`,...params.slug);
|
const fp = path.resolve('.',`bucket`,...params.slug);
|
||||||
return new Response(readFileSync(fp));
|
return new Response(readFileSync(fp));
|
||||||
}
|
}
|
||||||
@ -71,3 +71,12 @@ export async function updatePost(postAttributes: Partial<Attributes<Post>>): Pro
|
|||||||
const post = await Post.update(postAttributes, {where:{id:postAttributes.id}});
|
const post = await Post.update(postAttributes, {where:{id:postAttributes.id}});
|
||||||
return {result:JSON.parse(JSON.stringify(post))};
|
return {result:JSON.parse(JSON.stringify(post))};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PostServerActions = {
|
||||||
|
deletePost: (id: number) => Promise<ActionResult<boolean>>;
|
||||||
|
getPosts: () => Promise<ActionResult<GetPostsAttributes[]>>;
|
||||||
|
getProjects: () => Promise<ActionResult<Attributes<Project>[]>>;
|
||||||
|
savePost: (
|
||||||
|
data: Partial<Attributes<Post>>
|
||||||
|
) => Promise<ActionResult<Attributes<Post>[]>>;
|
||||||
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { GetPostsAttributes } from "@/app/lib/actions/entityManagement/postActio
|
|||||||
import { Post, Project, Bucket, PostBucket, Attachment } from "@/models";
|
import { Post, Project, Bucket, PostBucket, Attachment } from "@/models";
|
||||||
import { Attributes } from "@sequelize/core";
|
import { Attributes } from "@sequelize/core";
|
||||||
import { UUID } from "crypto";
|
import { UUID } from "crypto";
|
||||||
|
import { EntityEditorTextArea } from '../input/EntityEditorTextArea';
|
||||||
import {
|
import {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
MouseEventHandler,
|
MouseEventHandler,
|
||||||
@ -49,23 +50,6 @@ export default function PostEditor({
|
|||||||
let [postProjectIDState, setPostProjectIDState] = useState(
|
let [postProjectIDState, setPostProjectIDState] = useState(
|
||||||
editorPost.project.id
|
editorPost.project.id
|
||||||
);
|
);
|
||||||
let textbox: any = useRef(undefined);
|
|
||||||
|
|
||||||
// Autosize the text area
|
|
||||||
function textAreaAutoSize(
|
|
||||||
textbox: MutableRefObject<HTMLTextAreaElement>
|
|
||||||
): 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
|
|
||||||
const onTextAreaChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
|
||||||
setPostContentState(e.target.value); // Update State
|
|
||||||
textAreaAutoSize(textbox); // Autosize the text area
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle changing the selected project using the dropdown select
|
// Handle changing the selected project using the dropdown select
|
||||||
const projectSelectionChange: ChangeEventHandler<HTMLSelectElement> = (e) =>
|
const projectSelectionChange: ChangeEventHandler<HTMLSelectElement> = (e) =>
|
||||||
@ -102,14 +86,14 @@ export default function PostEditor({
|
|||||||
<h2 key="label-content" className="m-2">
|
<h2 key="label-content" className="m-2">
|
||||||
Content
|
Content
|
||||||
</h2>
|
</h2>
|
||||||
<textarea
|
<EntityEditorTextArea
|
||||||
key="input-content"
|
contentHook={{
|
||||||
onChange={onTextAreaChange}
|
state: postContentState,
|
||||||
ref={textbox}
|
setState: setPostContentState
|
||||||
value={postContentState}
|
}}
|
||||||
style={{ height: "100%" }}
|
className={ "w-[100%] min-h-auto h-content align-top text-start text-base line-clamp-6 m-2" }
|
||||||
className="w-[100%] min-h-auto h-content align-top text-start text-base line-clamp-6 m-2"
|
>
|
||||||
/>
|
</EntityEditorTextArea>
|
||||||
<h2 key="label-project" className="m-2">
|
<h2 key="label-project" className="m-2">
|
||||||
Project
|
Project
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -1,38 +1,28 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { ReactNode } from "react";
|
import React from "react";
|
||||||
import EntityManagementTable from "../EntityManagementTable";
|
import EntityManagementTable from "../EntityManagementTable";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { EditorRenderer, EditorState, PostTableCallbacks } from "./PostEditor";
|
import { EditorRenderer, EditorState, PostTableCallbacks } from "./PostEditor";
|
||||||
import { Attributes } from "@sequelize/core";
|
import { Attributes, InferAttributes } from "@sequelize/core";
|
||||||
import { useState } from "react";
|
import { Project, Post, Bucket, User, PostAttributesWithBuckets } from "@/models";
|
||||||
import { Project, Post, Bucket } from "@/models";
|
|
||||||
import { ActionResult } from "@/app/lib/actions/ActionResult";
|
|
||||||
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
|
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getPostsWithBucketsAndProject,
|
getPostsWithBucketsAndProject,
|
||||||
GetPostsAttributes,
|
GetPostsAttributes,
|
||||||
|
PostServerActions,
|
||||||
} from "@/app/lib/actions/entityManagement/postActions";
|
} from "@/app/lib/actions/entityManagement/postActions";
|
||||||
|
import { PostViewProps } from "@/views/admin/ClientPostView";
|
||||||
|
import { aifa } from "@/util/Utils";
|
||||||
|
import { StateHook } from "../../../util/types/StateHook";
|
||||||
|
|
||||||
export type PostTableServerActions = {
|
export type PostTableStateProps = {
|
||||||
deletePost: (id: number) => Promise<ActionResult<boolean>>;
|
posts:StateHook<GetPostsAttributes[]>,
|
||||||
getPosts: () => Promise<ActionResult<GetPostsAttributes[]>>;
|
editor:StateHook<EditorState>
|
||||||
getProjects: () => Promise<ActionResult<Attributes<Project>[]>>;
|
}
|
||||||
savePost: (
|
|
||||||
data: Partial<Attributes<Post>>
|
|
||||||
) => Promise<ActionResult<Attributes<Post>[]>>;
|
|
||||||
// uploadAttachment: ()=> Promise<ActionResult<any>>
|
|
||||||
};
|
|
||||||
|
|
||||||
type PostTableProps = {
|
export type PostTableProps = PostViewProps & {state:PostTableStateProps};
|
||||||
children?: ReactNode;
|
|
||||||
headings: Array<string>;
|
|
||||||
posts: GetPostsAttributes[];
|
|
||||||
projects: Attributes<Project>[];
|
|
||||||
actions: PostTableServerActions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const aifa = (a: ReactNode, b: ReactNode) => (a ? a : b);
|
|
||||||
|
|
||||||
export default function PostTable({
|
export default function PostTable({
|
||||||
children,
|
children,
|
||||||
@ -40,26 +30,20 @@ export default function PostTable({
|
|||||||
posts,
|
posts,
|
||||||
projects,
|
projects,
|
||||||
actions,
|
actions,
|
||||||
|
state,
|
||||||
}: PostTableProps) {
|
}: PostTableProps) {
|
||||||
// Init editor state. Make sure it is not opened by default
|
|
||||||
const initEditorState: EditorState = {
|
|
||||||
isEditorOpen: false,
|
|
||||||
editorPost: posts[0],
|
|
||||||
};
|
|
||||||
// Set up required state hooks
|
|
||||||
const [postsState, setPostsState] = useState(posts);
|
|
||||||
const [editorState, setEditorState] = useState(initEditorState);
|
|
||||||
|
|
||||||
// Define editor controls
|
// Define editor controls
|
||||||
const editorControls = {
|
const editorControls = {
|
||||||
closeEditor: () => {
|
closeEditor: () => {
|
||||||
setEditorState({
|
state.editor.setState({
|
||||||
isEditorOpen: false,
|
isEditorOpen: false,
|
||||||
editorPost: posts[0],
|
editorPost: posts[0],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showEditor: (entry: GetPostsAttributes) => {
|
showEditor: (entry: GetPostsAttributes) => {
|
||||||
setEditorState({
|
state.editor.setState({
|
||||||
isEditorOpen: true,
|
isEditorOpen: true,
|
||||||
editorPost: entry,
|
editorPost: entry,
|
||||||
});
|
});
|
||||||
@ -72,8 +56,8 @@ export default function PostTable({
|
|||||||
actions.deletePost(entry.id).then((actionResult) => {
|
actions.deletePost(entry.id).then((actionResult) => {
|
||||||
const result = handleActionResult(actionResult);
|
const result = handleActionResult(actionResult);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
postsState.splice(postsState.indexOf(entry), 1);
|
state.posts.state.splice(state.posts.state.indexOf(entry), 1);
|
||||||
setPostsState([...postsState]);
|
state.posts.setState([...state.posts.state]);
|
||||||
toast.success("Removed Post:" + entry.id);
|
toast.success("Removed Post:" + entry.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -86,7 +70,7 @@ export default function PostTable({
|
|||||||
getPostsServerActionResult
|
getPostsServerActionResult
|
||||||
);
|
);
|
||||||
// Set Posts state
|
// Set Posts state
|
||||||
if (result) setPostsState(result);
|
if (result) state.posts.setState(result);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
savePost: (e: Partial<Attributes<Post>>) => {
|
savePost: (e: Partial<Attributes<Post>>) => {
|
||||||
@ -101,26 +85,19 @@ export default function PostTable({
|
|||||||
.then(getPostsWithBucketsAndProject)
|
.then(getPostsWithBucketsAndProject)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const result = handleActionResult(res);
|
const result = handleActionResult(res);
|
||||||
if (result) setPostsState(result);
|
if (result) state.posts.setState(result);
|
||||||
})
|
})
|
||||||
.then(editorControls.closeEditor)
|
.then(editorControls.closeEditor)
|
||||||
.then(postActions.refetch);
|
.then(postActions.refetch);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const callbacks: PostTableCallbacks = {
|
|
||||||
savePost: postActions.savePost,
|
|
||||||
closeEditor: editorControls.closeEditor,
|
|
||||||
refetch: postActions.refetch,
|
|
||||||
uploadAttachment: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityManagementTable
|
<EntityManagementTable
|
||||||
entityName="Post"
|
entityName="Post"
|
||||||
headings={headings}
|
headings={headings}
|
||||||
>
|
>
|
||||||
{postsState.map((post: GetPostsAttributes) => (
|
{state.posts.state.map((post: GetPostsAttributes) => (
|
||||||
<React.Fragment key={`postrow-${post.id}`}>
|
<React.Fragment key={`postrow-${post.id}`}>
|
||||||
<tr>
|
<tr>
|
||||||
<th key={`rowheading-post-${post.id}`} scope="row">
|
<th key={`rowheading-post-${post.id}`} scope="row">
|
||||||
@ -177,8 +154,13 @@ export default function PostTable({
|
|||||||
headings={headings}
|
headings={headings}
|
||||||
editorPost={post}
|
editorPost={post}
|
||||||
editorControls={editorControls}
|
editorControls={editorControls}
|
||||||
editorState={editorState}
|
editorState={state.editor.state}
|
||||||
callbacks={callbacks}
|
callbacks={{
|
||||||
|
savePost: postActions.savePost,
|
||||||
|
closeEditor: editorControls.closeEditor,
|
||||||
|
refetch: postActions.refetch,
|
||||||
|
uploadAttachment: () => {},
|
||||||
|
}}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
34
src/components/client/input/EntityEditorTextArea.tsx
Normal file
34
src/components/client/input/EntityEditorTextArea.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRef, MutableRefObject, useLayoutEffect, ChangeEventHandler, useState } from "react";
|
||||||
|
import { StateHook } from '../../../util/types/StateHook';
|
||||||
|
|
||||||
|
export function EntityEditorTextArea({contentHook, className}: {contentHook:StateHook<string>, className:string}){
|
||||||
|
|
||||||
|
let textbox: any = useRef(undefined);
|
||||||
|
|
||||||
|
// Autosize the text area
|
||||||
|
function textAreaAutoSize(
|
||||||
|
textbox: MutableRefObject<HTMLTextAreaElement>
|
||||||
|
): 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
|
||||||
|
const onTextAreaChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||||
|
contentHook?.setState(e.target.value); // Update State
|
||||||
|
textAreaAutoSize(textbox); // Autosize the text area
|
||||||
|
};
|
||||||
|
|
||||||
|
return <textarea
|
||||||
|
key="input-content"
|
||||||
|
onChange={onTextAreaChange}
|
||||||
|
ref={textbox}
|
||||||
|
value={contentHook?.state}
|
||||||
|
style={{ height: "100%" }}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ export class User extends Model<InferAttributes<User>, InferCreationAttributes<U
|
|||||||
@Unique
|
@Unique
|
||||||
declare id: CreationOptional<number>;
|
declare id: CreationOptional<number>;
|
||||||
@Attribute(DataTypes.STRING)
|
@Attribute(DataTypes.STRING)
|
||||||
|
@Unique()
|
||||||
declare username: string;
|
declare username: string;
|
||||||
@Attribute(DataTypes.STRING)
|
@Attribute(DataTypes.STRING)
|
||||||
declare password: string;
|
declare password: string;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
'server only'
|
'server only'
|
||||||
|
import { ReactNode } from "react";
|
||||||
import Gens from "./gens";
|
import Gens from "./gens";
|
||||||
// import Auth from "./Auth";
|
// import Auth from "./Auth";
|
||||||
|
|
||||||
@ -29,3 +30,4 @@ function truncateString(str:string = '', num:number = 255) {
|
|||||||
|
|
||||||
|
|
||||||
export { Gens, constructAPIUrl, constructUrl, truncateString }
|
export { Gens, constructAPIUrl, constructUrl, truncateString }
|
||||||
|
export const aifa = (a: ReactNode, b: ReactNode) => (a ? a : b);
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { validatePassword } from "@/util/Auth";
|
import { validatePassword } from "@/util/Auth";
|
||||||
import { APIError } from "@/util/api/error";
|
import { APIError } from "@/util/api/error";
|
||||||
import { User } from "@/model/User";
|
import { User } from "@/models";
|
||||||
|
|
||||||
export function parseBasicAuth(authb64:string):UserAuth
|
export function parseBasicAuth(authb64:string):UserAuth
|
||||||
{
|
{
|
||||||
|
|||||||
9
src/util/types/StateHook.tsx
Normal file
9
src/util/types/StateHook.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
export type StateHook<T> = {
|
||||||
|
state: T;
|
||||||
|
setState: Dispatch<SetStateAction<T>>;
|
||||||
|
};
|
||||||
|
export type StateHookArray<T> = [
|
||||||
|
T,Dispatch<SetStateAction<T>>
|
||||||
|
]
|
||||||
62
src/views/admin/ClientPostView.tsx
Normal file
62
src/views/admin/ClientPostView.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PostTable, {
|
||||||
|
PostTableStateProps,
|
||||||
|
} from "@/components/client/admin/PostTable";
|
||||||
|
import { PostTableProps } from "../../components/client/admin/PostTable";
|
||||||
|
import {
|
||||||
|
GetPostsAttributes,
|
||||||
|
PostServerActions,
|
||||||
|
} from "@/app/lib/actions/entityManagement/postActions";
|
||||||
|
import { EditorState } from "@/components/client/admin/PostEditor";
|
||||||
|
import { Project } from "@/models";
|
||||||
|
import { Dispatch, ReactNode, SetStateAction, useState } from "react";
|
||||||
|
import { Attributes } from "@sequelize/core";
|
||||||
|
import { StateHookArray } from "@/util/types/StateHook";
|
||||||
|
|
||||||
|
export type PostViewProps = {
|
||||||
|
children?: ReactNode;
|
||||||
|
headings: Array<string>;
|
||||||
|
posts: GetPostsAttributes[];
|
||||||
|
projects: Attributes<Project>[];
|
||||||
|
actions: PostServerActions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ClientPostView({
|
||||||
|
children,
|
||||||
|
headings,
|
||||||
|
posts,
|
||||||
|
projects,
|
||||||
|
actions,
|
||||||
|
}: PostViewProps) {
|
||||||
|
// Init editor state. Make sure it is not opened by default
|
||||||
|
const initEditorState: EditorState = {
|
||||||
|
isEditorOpen: false,
|
||||||
|
editorPost: posts[0],
|
||||||
|
};
|
||||||
|
// Set up required state hooks
|
||||||
|
const [postsState, setPostsState]: StateHookArray<GetPostsAttributes[]> = useState(posts);
|
||||||
|
const [editorState, setEditorState]: StateHookArray<EditorState> = useState(initEditorState);
|
||||||
|
const state: PostTableStateProps = {
|
||||||
|
posts: {
|
||||||
|
state: postsState,
|
||||||
|
setState: setPostsState,
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
state: editorState,
|
||||||
|
setState: setEditorState,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PostTable
|
||||||
|
headings={headings}
|
||||||
|
posts={posts}
|
||||||
|
projects={projects}
|
||||||
|
actions={actions}
|
||||||
|
state={state}
|
||||||
|
></PostTable>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,25 +2,29 @@
|
|||||||
cache: "no-store";
|
cache: "no-store";
|
||||||
|
|
||||||
import { ReactNode, useEffect } from "react";
|
import { ReactNode, useEffect } from "react";
|
||||||
import PostTable, {
|
import PostTable from "@/components/client/admin/PostTable";
|
||||||
PostTableServerActions,
|
|
||||||
} from "@/components/client/admin/PostTable";
|
|
||||||
import {
|
import {
|
||||||
deletePost,
|
deletePost,
|
||||||
getPostsWithBucketsAndProject,
|
getPostsWithBucketsAndProject,
|
||||||
GetPostsAttributes,
|
|
||||||
updatePost,
|
updatePost,
|
||||||
|
GetPostsAttributes,
|
||||||
|
PostServerActions,
|
||||||
} from "@/app/lib/actions/entityManagement/postActions";
|
} from "@/app/lib/actions/entityManagement/postActions";
|
||||||
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
||||||
import { Bucket, Project, Post, dbSync, Attachment } from "@/models";
|
import { Bucket, Project, Post, dbSync, Attachment } from "@/models";
|
||||||
import { handleActionResult } from "../../app/lib/actions/clientActionHandler";
|
import { handleActionResult } from "../../app/lib/actions/clientActionHandler";
|
||||||
|
import { ClientPostView } from "./ClientPostView";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getHeadings() {
|
export default async function PostView(props: Props) {
|
||||||
let headings: string[] = [
|
const sync = await dbSync;
|
||||||
|
|
||||||
|
const headings:string[] = [
|
||||||
"#",
|
"#",
|
||||||
"Title",
|
"Title",
|
||||||
"Content",
|
"Content",
|
||||||
@ -29,15 +33,7 @@ async function getHeadings() {
|
|||||||
"UpdatedAt",
|
"UpdatedAt",
|
||||||
];
|
];
|
||||||
|
|
||||||
return headings;
|
const actions: PostServerActions = {
|
||||||
}
|
|
||||||
|
|
||||||
export default async function PostView(props: Props) {
|
|
||||||
const sync = await dbSync;
|
|
||||||
|
|
||||||
const headings = await getHeadings();
|
|
||||||
|
|
||||||
const actions: PostTableServerActions = {
|
|
||||||
deletePost: deletePost,
|
deletePost: deletePost,
|
||||||
getPosts: getPostsWithBucketsAndProject,
|
getPosts: getPostsWithBucketsAndProject,
|
||||||
getProjects: getProjects,
|
getProjects: getProjects,
|
||||||
@ -55,12 +51,12 @@ export default async function PostView(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
|
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
|
||||||
<div className="w-[100%] m-auto">
|
<div className="w-[100%] m-auto">
|
||||||
<PostTable
|
<ClientPostView
|
||||||
posts={posts ? posts : []}
|
posts={posts ? posts : []}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
headings={headings}
|
headings={headings}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
></PostTable>
|
></ClientPostView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,58 +1,63 @@
|
|||||||
cache: 'no-store'
|
cache: "no-store";
|
||||||
|
|
||||||
import { tryFetchPosts } from "@/app/api/post/route";
|
import { tryFetchPosts } from "@/app/api/post/route";
|
||||||
import { constructAPIUrl } from "@/util/Utils";
|
import { constructAPIUrl } from "@/util/Utils";
|
||||||
import { ReactNode, useEffect } from "react";
|
import { ReactNode, useEffect } from "react";
|
||||||
import EntityManagementTable from "../../components/client/EntityManagementTable";
|
import EntityManagementTable from "../../components/client/EntityManagementTable";
|
||||||
import PostTable from "@/components/client/admin/PostTable";
|
import PostTable from "@/components/client/admin/PostTable";
|
||||||
import { deletePost, getPostsWithBucketsAndProject, updatePost } from "@/app/lib/actions/entityManagement/postActions";
|
import {
|
||||||
|
deletePost,
|
||||||
|
getPostsWithBucketsAndProject,
|
||||||
|
updatePost,
|
||||||
|
} from "@/app/lib/actions/entityManagement/postActions";
|
||||||
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
||||||
import { Bucket, Project, Post, dbSync, Attachment } from "@/models";
|
import { Bucket, Project, Post, dbSync, Attachment } from "@/models";
|
||||||
import { tryCreateAttachment } from "@/app/api/attachment/route";
|
import { tryCreateAttachment } from "@/app/api/attachment/route";
|
||||||
import { Attributes } from '@sequelize/core';
|
import { Attributes } from "@sequelize/core";
|
||||||
import * as util from 'util'
|
import * as util from "util";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?:ReactNode
|
children?: ReactNode;
|
||||||
}
|
};
|
||||||
|
|
||||||
async function getHeadings(){
|
async function getHeadings() {
|
||||||
let headings:string[] = []
|
let headings: string[] = [];
|
||||||
for (const key in Project.getAttributes())
|
for (const key in Project.getAttributes()) {
|
||||||
{
|
headings.push(key);
|
||||||
headings.push(key)
|
|
||||||
}
|
}
|
||||||
return headings
|
return headings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ProjectView(props:Props){
|
export default async function ProjectView(props: Props) {
|
||||||
const sync = await dbSync;
|
const sync = await dbSync;
|
||||||
|
|
||||||
// const headings = [
|
const actions = {
|
||||||
// '#',
|
deletePost: deletePost,
|
||||||
// 'Title',
|
getProjects:getProjects,
|
||||||
// 'Content',
|
savePost:updatePost,
|
||||||
// 'Project',
|
// uploadAttachment:tryCreateAttachment
|
||||||
// 'Date Created',
|
};
|
||||||
// 'Date Modified',
|
const projects: Project[] = await Project.findAll().then((projects) =>
|
||||||
// 'Edit',
|
projects.map((e) => JSON.parse(JSON.stringify(e)))
|
||||||
// 'Delete',
|
);
|
||||||
// ]
|
return (
|
||||||
// const actions = {
|
<>
|
||||||
// deletePost: deletePost,
|
|
||||||
// getPosts:getPostsWithBuckets,
|
|
||||||
// getProjects:getProjects,
|
|
||||||
// savePost:updatePost,
|
|
||||||
// // uploadAttachment:tryCreateAttachment
|
|
||||||
// };
|
|
||||||
|
|
||||||
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))));
|
|
||||||
const headings = await getHeadings();
|
|
||||||
return <>
|
|
||||||
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
|
<div className="w-[100%] min-h-fit bg-gray-100 overflow-scroll">
|
||||||
<div className="w-[100%] m-auto">
|
<div className="w-[100%] m-auto">
|
||||||
<EntityManagementTable entityName="Project" headings={headings}><></></EntityManagementTable>
|
<EntityManagementTable
|
||||||
|
entityName="Project"
|
||||||
|
headings={[
|
||||||
|
'#',
|
||||||
|
'Title',
|
||||||
|
'Content',
|
||||||
|
'Project',
|
||||||
|
'Date Created',
|
||||||
|
'Date Modified',
|
||||||
|
'Edit',
|
||||||
|
'Delete',
|
||||||
|
]}>
|
||||||
|
{""}
|
||||||
|
</EntityManagementTable>
|
||||||
{/* <PostTable data={posts}
|
{/* <PostTable data={posts}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
headings={headings}
|
headings={headings}
|
||||||
@ -60,5 +65,6 @@ export default async function ProjectView(props:Props){
|
|||||||
</PostTable> */}
|
</PostTable> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user