more refactoring

This commit is contained in:
Andreas 2024-07-02 09:08:49 +02:00
parent de148eaf19
commit 1971966099
27 changed files with 400 additions and 324 deletions

View File

@ -1,167 +1,154 @@
'use server'
"use server";
import { APIError, attemptAPIAction } from "@/util/api/error";
import { sequelize, Bucket, Auth, Post, PostTag, Tag, User, dbSync } from "@/models";
import { APIError, attemptAPIAction } from "@/util";
import {
sequelize,
Bucket,
Auth,
Post,
PostTag,
Tag,
User,
dbSync,
} from "@/models";
import { cookies } from "next/headers";
import { Attachment } from "@/models";
import { UUID } from "crypto";
import { mkdir, mkdirSync, writeFile } from "fs";
import { where } from "@sequelize/core";
import { addToNewBucketForPost } from "../../lib/actions/entityManagement/attachment/attachmentActions";
async function writeFilesToFS(uuid: UUID, files: any[]): Promise<{ success: boolean, filePaths: string[] }> {
type FileArrayEntryWithPromise = {
name: string;
content: Promise<ReadableStreamReadResult<Uint8Array>> | ReadableStreamReadResult<Uint8Array>
}
type FileArrayEntry = {
name: string;
content: ReadableStreamReadResult<Uint8Array>
};
// Resolve promises
const fileArray: FileArrayEntryWithPromise[] = await Promise.all(files.map((file: File) => {
return { 'name': (() => file.name)(), 'content': file.stream().getReader().read() };
}));
let finalFileArray: FileArrayEntry[] = await (
async () => {
for (let file in fileArray) {
fileArray[file].content = await fileArray[file].content
}
return [...fileArray as FileArrayEntry[]]
})();
// Make Bucket Directory
mkdirSync(`./bucket/${uuid}/`, { recursive: true })
// Write files to filesystem
for (let file in finalFileArray) {
writeFile(`./bucket/${uuid}/${finalFileArray[file].name}`
, Buffer.from(finalFileArray[file].content.value as Uint8Array)
, (e) => { console.log(e) });
}
return {
success: true,
filePaths: finalFileArray.map((e) => e.name)
}
}
async function addToExistingBucket(bucketid: number): Promise<Bucket> {
const bucket = await Bucket.findOne({
where: { id: bucketid }, include: { association: Bucket.associations.posts }
});
if (!bucket) throw new APIError({ status: 500, responseText: "invalid bucketid" });
return bucket;
}
import {
addToExistingBucket,
addToNewBucketForPost,
writeFilesToFS,
} from "@/app/lib/actions/entityManagement/attachment/attachmentActions";
import { Attributes } from "@sequelize/core";
import { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies";
export async function tryCreateAttachment(request: Request) {
// Make sure the DB is ready
await dbSync;
// Make sure the DB is ready
await dbSync;
// Prepare data
const formData = await request.formData();
const requestData: string | Object | undefined = formData
.get("data")
?.valueOf();
const files: FormDataEntryValue[] = formData.getAll("files");
const authCkie: RequestCookie | undefined = await cookies().get("auth");
// Prepare data
const formData = await request.formData();
const requestData: string | Object | undefined = formData.get('data')?.valueOf();
const files: FormDataEntryValue[] = formData.getAll('files')
const authCkie = await cookies().get("auth");
// Sanity check auth cookie
if (!authCkie || !authCkie.value)
throw new APIError({
status: 500,
responseText: "missing auth cookie",
});
// Sanity check auth cookie
if (!authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" });
// Get JSON from the Cookie
const cookieJSON: string = authCkie.value;
const authObject:Attributes<Auth> = JSON.parse(cookieJSON);
// Get JSON from the Cookie
const cookieJSON = authCkie.value;
const authObject = JSON.parse(cookieJSON);
// Fetch User Auth from thse database
const auth = await Auth.findOne({
include: [
{
model: User.withScope(["withPerms"]),
attributes: {
exclude: ["username", "password", "updatedAt", "createdAt"],
},
},
],
where: { token: authObject.token },
});
// Fetch User Auth from the database
const auth = await Auth.findOne({
include: [
{
model: User.withScope(['withPerms']),
attributes: {
exclude: ['username', 'password', 'updatedAt', 'createdAt']
}
}
],
where: { token: authObject.token }
});
// Sanity check the auth and associated user for authorization
if (!auth || !auth.user)
throw new APIError({
status: 401,
responseText: "Authentication Error",
});
if (!auth.user.id)
throw new APIError({ status: 401, responseText: "Missing user id" });
if (!auth.user.perms || !auth.user.perms.isAdmin)
throw new APIError({ status: 401, responseText: `Unauthorized` });
// Handle incomplete data or other problems
if (!files)
throw new APIError({ status: 500, responseText: "Missing file" });
if (!formData)
throw new APIError({ status: 500, responseText: "Empty request body" });
if (!requestData)
throw new APIError({
status: 500,
responseText: "Missing request data",
});
if (!(typeof requestData == "string"))
throw new APIError({
status: 500,
responseText: "Malformed request data",
});
// Parse JSON
const data = JSON.parse(requestData);
// Sanity check the auth and associated user for authorization
if (!auth
|| !auth.user) throw new APIError({ status: 401, responseText: "Authentication Error" });
if (!auth.user.id) throw new APIError({ status: 401, responseText: "Missing user id" });
if (!auth.user.perms
|| !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized` });
// Handle incomplete data or other problems
if (!files) throw new APIError({ status: 500, responseText: "Missing file" });
if (!formData) throw new APIError({ status: 500, responseText: "Empty request body" });
if (!requestData) throw new APIError({ status: 500, responseText: "Missing request data" });
if (!(typeof requestData == "string")) throw new APIError({ status: 500, responseText: "Malformed request data" });
// Get or create bucket
const bucket: Bucket =
data.postid && !data.bucketid
? await addToNewBucketForPost(data.postid)
: await addToExistingBucket(data.bucketid);
// Write files to bucket and store as attachments in DB
const writeResult = await writeFilesToFS(bucket.id, files);
// Handle failure
if (!writeResult.success)
throw new APIError({
status: 500,
responseText: "Error writing files to Bucket",
});
// Parse JSON
const data = JSON.parse(requestData);
// Write attachments to db
const attachments = writeResult.filePaths.map(async (fp) => {
return await Attachment.create(
{
bucket_id: bucket.id,
filename: fp,
},
{ include: Attachment.associations.bucket }
);
});
attachments;
// Get or create bucket
const bucket: Bucket = (data.postid && !data.bucketid)
? await addToNewBucketForPost(data.postid)
: await addToExistingBucket(data.bucketid);
// Write files to bucket and store as attachments in DB
const writeResult = await writeFilesToFS(bucket.id, files);
// Handle failure
if (!writeResult.success) throw new APIError({ status: 500, responseText: "Error writing files to Bucket" });
// Write attachments to db
const attachments = ((writeResult).filePaths.map(async (fp) => {
return (await Attachment.create(
{
bucket_id: bucket.id,
filename: fp
},
{ include: Attachment.associations.bucket }
));
}))
attachments;
return new Response(
JSON.stringify({
bucket: await Bucket.findOne({
where: { id: bucket.id },
include: [Bucket.associations.posts, Bucket.associations.attachments]
})
}),
{ status: 200 }
);
return new Response(
JSON.stringify({
bucket: await Bucket.findOne({
where: { id: bucket.id },
include: [
Bucket.associations.posts,
Bucket.associations.attachments,
],
}),
}),
{ status: 200 }
);
}
export async function tryFetchAttachments(request: Request) {
await Post.sync();
await Post.sync();
const foundPosts = await Post.findAll({
include: [
{
association: Post.associations.user,
attributes: { exclude: ['password', 'createdAt', 'updatedAt'] }
}, {
association: Post.associations.postTags
}]
});
return new Response(JSON.stringify(foundPosts), { status: 200 });
const foundPosts = await Post.findAll({
include: [
{
association: Post.associations.user,
attributes: { exclude: ["password", "createdAt", "updatedAt"] },
},
{
association: Post.associations.postTags,
},
],
});
return new Response(JSON.stringify(foundPosts), { status: 200 });
}
export async function GET(request: Request) {
return await attemptAPIAction(tryFetchAttachments, request);
return await attemptAPIAction(tryFetchAttachments, request);
}
export async function POST(request: Request) {
return await attemptAPIAction(tryCreateAttachment, request);
return await attemptAPIAction(tryCreateAttachment, request);
}

View File

@ -1,84 +1,73 @@
'use server'
"use server";
import { cookies } from "next/headers";
import { APIError} from "@/util/api/error"
import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user"
import { APIError, UserAuth, parseBasicAuth, getAssociatedUser } from "@/util";
import { Auth, User } from "@/models";
async function tryAuth(request: Request) {
// await User.sync();
await Auth.sync();
async function tryAuth(request:Request){
const auth: string | null = request.headers.get("authorization");
// await User.sync();
await Auth.sync();
if (!auth) {
return new Response("unauthorized", { status: 403 });
}
const auth:string|null = request.headers.get("authorization");
const userAuth = parseBasicAuth(auth);
const user = await getAssociatedUser(userAuth);
if(!auth){
return new Response("unauthorized",{status:403});
}
if (!user || !user.id) return new Response("error", { status: 500 });
const userAuth = parseBasicAuth(auth);
const user = await getAssociatedUser(userAuth);
const authentication = await Auth.create({
user_id: user.id,
});
if (!user || !user.id)
return new Response("error",{status:500});
console.log("ok");
const foundAuth = await Auth.findOne({
include: {
model: User,
as: "user",
},
where: {
user_id: user.id,
},
});
console.log("ok2");
const authentication = await Auth.create({
user_id: user.id,
})
if (!foundAuth) return new Response("error", { status: 500 });
console.log('ok');
const foundAuth = await Auth.findOne({
include: {
model: User,
as: 'user'
},
where: {
user_id: user.id
}
})
console.log('ok2');
const usr = foundAuth.user;
if(!foundAuth)
return new Response("error",{status:500});
const authUser = await authentication.getUser();
const usr = foundAuth.user;
// @ts-ignore
cookies().set("auth", JSON.stringify(authentication));
const authUser = await authentication.getUser();
// @ts-ignore
cookies().set('auth', JSON.stringify(authentication));
return new Response(
JSON.stringify(
{
credentials: userAuth,
auth: authentication,
user: authUser,
foundAuth: foundAuth
}),
{
status: 200,
headers:{
"Content-Type": "text/JSON"
}
}
);
return new Response(
JSON.stringify({
credentials: userAuth,
auth: authentication,
user: authUser,
foundAuth: foundAuth,
}),
{
status: 200,
headers: {
"Content-Type": "text/JSON",
},
}
);
}
export async function POST(request:Request){
try{
return await tryAuth(request);
}
catch(e){
if (e instanceof APIError){
return new Response(e.info.responseText,{status:e.info.status});
}
else{
throw e;
}
}
export async function POST(request: Request) {
try {
return await tryAuth(request);
} catch (e) {
if (e instanceof APIError) {
return new Response(e.info.responseText, { status: e.info.status });
} else {
throw e;
}
}
}

View File

@ -7,7 +7,7 @@ import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user"
import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms,dbSync,sequelize} from "@/models";
import Sequelize, { CreationAttributes, DataTypes } from "@sequelize/core";
import { SqliteColumnsDescription, SqliteDialect, SqliteQueryInterface } from "@sequelize/sqlite3";
import { hashPassword } from "@/util/Auth";
import { hashpassword } from "@/util/auth";
import { copyFile, readFileSync } from "fs";
import path from "path";
import { Attributes } from '@sequelize/core';
@ -19,7 +19,7 @@ async function seedUsers(qif: SqliteQueryInterface<SqliteDialect>){
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);
user.password = await hashpassword(user.password);
return user;
})

View File

@ -11,7 +11,7 @@
import { APIError } from "@/util/api/error";
import { UserAuth } from "@/util/api/user";
import { Attachment, Auth, Bucket, DBState, Post, PostTag, Project, Tag, User, UserPerms, addUserScopes, dbSync,sequelize} from "@/models";
import { hashPassword } from "@/util/Auth";
import { hashpassword } from "@/util/auth";
// Attempt to register a new User
@ -32,7 +32,7 @@ async function attemptRegister(request:Request){
// Hash the password and create a new user in the database
const user = await User.create({
username: requestBody.username,
password: await hashPassword(requestBody.password),
password: await hashpassword(requestBody.password),
perms:{
isAdmin: false
}

View File

@ -1,6 +1,6 @@
'use server'
import { constructAPIUrl } from "@/util/Utils"
import { constructAPIUrl } from "@/util/url";
import { cookies } from "next/headers"
import { parseSetCookie } from "@/util/parseSetCookie";
import makeFetchCookie from 'fetch-cookie';

View File

@ -1,13 +1,65 @@
'use server';
import { APIError } from "@/util/api/error";
import { Bucket, Post } from "@/models";
import { randomUUID } from "crypto";
import { randomUUID, UUID } from "crypto";
import { mkdirSync, writeFile } from "fs";
export async function addToNewBucketForPost(postid: number): Promise<Bucket> {
Post.sync();
const post = await Post.findOne({ where: { id: postid } });
const post: Post | null = await Post.findOne({ where: { id: postid } });
if (!post) throw new APIError({ status: 500, responseText: "invalid postid" });
const bucket = await post.createBucket({ id: randomUUID() });
return bucket;
}
export async function writeFilesToFS(uuid: UUID, files: any[]): Promise<{ success: boolean, filePaths: string[] }> {
type FileArrayEntryWithPromise = {
name: string;
content: Promise<ReadableStreamReadResult<Uint8Array>> | ReadableStreamReadResult<Uint8Array>
}
type FileArrayEntry = {
name: string;
content: ReadableStreamReadResult<Uint8Array>
};
// Resolve promises
const fileArray: FileArrayEntryWithPromise[] = await Promise.all(files.map((file: File) => {
return { 'name': (() => file.name)(), 'content': file.stream().getReader().read() };
}));
let finalFileArray: FileArrayEntry[] = await (
async () => {
for (let file in fileArray) {
fileArray[file].content = await fileArray[file].content
}
return [...fileArray as FileArrayEntry[]]
})();
// Make Bucket Directory
mkdirSync(`./bucket/${uuid}/`, { recursive: true })
// Write files to filesystem
for (let file in finalFileArray) {
writeFile(`./bucket/${uuid}/${finalFileArray[file].name}`
, Buffer.from(finalFileArray[file].content.value as Uint8Array)
, (e) => { console.log(e) });
}
return {
success: true,
filePaths: finalFileArray.map((e) => e.name)
}
}
export async function addToExistingBucket(bucketid: number): Promise<Bucket> {
const bucket = await Bucket.findOne({
where: { id: bucketid },
include: { association: Bucket.associations.posts },
});
if (!bucket)
throw new APIError({ status: 500, responseText: "invalid bucketid" });
return bucket;
}

View File

@ -6,8 +6,8 @@ import ArticlePreview from "@/components/shared/news/article-preview"
import ReactDOM from "react";
import "/public/global.css"
import "./index.css"
import { Post } from "@/model/Post";
import { constructAPIUrl } from "@/util/Utils";
import { Post } from "@/models";
import { constructAPIUrl } from "@/util";
import Link from "next/link";
import { Attributes } from "@sequelize/core";

View File

@ -4,7 +4,13 @@ import EntityManagementTable from "../EntityManagementTable";
import toast from "react-hot-toast";
import { EditorRenderer, EditorState, PostTableCallbacks } from "./PostEditor";
import { Attributes, InferAttributes } from "@sequelize/core";
import { Project, Post, Bucket, User, PostAttributesWithBuckets } from "@/models";
import {
Project,
Post,
Bucket,
User,
PostAttributesWithBuckets,
} from "@/models";
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
import {
@ -13,16 +19,15 @@ import {
PostServerActions,
} from "@/app/lib/actions/entityManagement/post/postActions";
import { PostViewProps } from "@/views/admin/ClientPostView";
import { aifa } from "@/util/Utils";
import { StateHook } from "@/util/state/stateUtils";
import { aifa } from "@/util/utils";
import { StateHook } from "@/util/state";
export type PostTableStateProps = {
posts:StateHook<GetPostsAttributes[]>,
editor:StateHook<EditorState>
}
export type PostTableProps = PostViewProps & {state:PostTableStateProps};
posts: StateHook<GetPostsAttributes[]>;
editor: StateHook<EditorState>;
};
export type PostTableProps = PostViewProps & { state: PostTableStateProps };
export default function PostTable({
children,
@ -32,8 +37,6 @@ export default function PostTable({
actions,
state,
}: PostTableProps) {
// Define editor controls
const editorControls = {
closeEditor: () => {
@ -93,10 +96,7 @@ export default function PostTable({
};
return (
<EntityManagementTable
entityName="Post"
headings={headings}
>
<EntityManagementTable entityName="Post" headings={headings}>
{state.posts.state.map((post: GetPostsAttributes) => (
<React.Fragment key={`postrow-${post.id}`}>
<tr>

View File

@ -2,7 +2,7 @@
import { serverAttemptAuthenticateUser } from "@/app/lib/actions/actions";
import { AuthContext } from "@/providers/providers";
import { constructAPIUrl } from "@/util/Utils";
import { constructAPIUrl } from "@/util/url";
import { createContext, useState } from "react";
import { useFormState, useFormStatus } from "react-dom";

View File

@ -1,20 +1,34 @@
'use client'
"use client";
import { useRef, MutableRefObject, useLayoutEffect, ChangeEventHandler, useState } from "react";
import { StateHook } from '../../../util/types/StateHook';
import {
useRef,
MutableRefObject,
useLayoutEffect,
ChangeEventHandler,
useState,
} from "react";
import { StateHook } from "@/util";
export function EntityEditorTextArea({contentHook, className}: {contentHook:StateHook<string>, className:string}){
// 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`;
}
let textbox: any = useRef(undefined);
type EntityEditorTextAreaProps = {
contentHook: StateHook<string>;
className: string;
};
export function EntityEditorTextArea({
contentHook,
className,
}: EntityEditorTextAreaProps) {
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
@ -23,12 +37,14 @@ export function EntityEditorTextArea({contentHook, className}: {contentHook:Stat
textAreaAutoSize(textbox); // Autosize the text area
};
return <textarea
key="input-content"
onChange={onTextAreaChange}
ref={textbox}
value={contentHook?.state}
style={{ height: "100%" }}
className={className}
/>
return (
<textarea
key="input-content"
onChange={onTextAreaChange}
ref={textbox}
value={contentHook?.state}
style={{ height: "100%" }}
className={className}
/>
);
}

View File

@ -8,7 +8,7 @@ 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 { truncateString } from "@/util/utils";
// @ts-ignore
import { MDXRemote } from "next-mdx-remote/rsc";
import { ExampleComponent } from "./article";

View File

@ -1,16 +0,0 @@
'server only'
import { hash, compare } from "bcrypt";
export async function validatePassword(password:string, hashString:string){
const result = await compare(password, hashString);
return result;
}
export async function hashPassword(password:string){
const hashString = await hash(password, 10);
return hashString;
}
export default { validatePassword, hashPassword };

View File

@ -1,25 +1,6 @@
'server only'
import { ReactNode } from "react";
import Gens from "./gens";
// import Auth from "./Auth";
function getAPIEnv(){
return {
'schema': process.env.API_SCHEMA,
'host': process.env.API_HOST,
'port': process.env.API_PORT,
'basepath': process.env.API_BASEPATH
};
}
function constructAPIUrl(endpoint:string){
const { schema, host, port, basepath } = getAPIEnv();
return `${schema}://${host}:${port}/${basepath}/${endpoint}`
}
function constructUrl(endpoint:string){
const { schema, host, port, basepath } = getAPIEnv();
return `${schema}://${host}:${port}/${endpoint}`
}
function truncateString(str:string = '', num:number = 255) {
if (str.length > num) {
return str.slice(0, num) + "...";
@ -29,5 +10,5 @@ function truncateString(str:string = '', num:number = 255) {
}
export { Gens, constructAPIUrl, constructUrl, truncateString }
export { Gens, truncateString }
export const aifa = (a: ReactNode, b: ReactNode) => (a ? a : b);

2
src/util/api/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './error';
export * from './user';

View File

@ -1,34 +1,44 @@
// import { MUser } from "@/model/sequelize/User";
// import { MAuth } from "@/model/sequelize/Auth";
import { validatePassword } from "@/util/Auth";
import { validatepassword } from "@/util/auth";
import { APIError } from "@/util/api/error";
import { User } from "@/models";
export function parseBasicAuth(authb64:string):UserAuth
{
const authString:string = Buffer.from(authb64.split(" ")[1] as any, "base64").toString("utf8");
var userAuth:UserAuth = {
username:authString.split(":")[0] as any,
password:authString.split(":")[1] as any
};
return userAuth
export function parseBasicAuth(authb64: string): UserAuth {
const authString: string = Buffer.from(
authb64.split(" ")[1] as any,
"base64"
).toString("utf8");
var userAuth: UserAuth = {
username: authString.split(":")[0] as any,
password: authString.split(":")[1] as any,
};
return userAuth;
}
export type UserAuth = {
username: string,
password: string
}
export async function getAssociatedUser(auth:UserAuth)
{
let foundUser = await User.findOne({ attributes: {include: ['password']}, where: {username: auth.username} });
if (!foundUser)
throw new APIError({status: 401, responseText:"Unauthorized: Invalid Username"});
if (!(await validatePassword(auth.password, foundUser.password)))
throw new APIError({status: 401, responseText:"Unauthorized: Invalid Password"});
return foundUser;
username: string;
password: string;
};
export async function getAssociatedUser(auth: UserAuth) {
let foundUser = await User.findOne({
attributes: { include: ["password"] },
where: { username: auth.username },
});
if (!foundUser)
throw new APIError({
status: 401,
responseText: "Unauthorized: Invalid Username",
});
if (!(await validatepassword(auth.password, foundUser.password)))
throw new APIError({
status: 401,
responseText: "Unauthorized: Invalid Password",
});
return foundUser;
}

View File

@ -0,0 +1,8 @@
'server only'
import { hash } from "bcrypt";
export async function hashpassword(password:string){
const hashString = await hash(password, 10);
return hashString;
}

2
src/util/auth/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './hashpassword';
export * from './validatepassword';

View File

@ -0,0 +1,9 @@
'server only';
import { compare } from "bcrypt";
export async function validatepassword(password: string, hashString: string) {
const result = await compare(password, hashString);
return result;
}

10
src/util/getAPIEnv.ts Normal file
View File

@ -0,0 +1,10 @@
'server only';
// import Auth from "./Auth";
export function getAPIEnv() {
return {
'schema': process.env.API_SCHEMA,
'host': process.env.API_HOST,
'port': process.env.API_PORT,
'basepath': process.env.API_BASEPATH
};
}

10
src/util/index.ts Normal file
View File

@ -0,0 +1,10 @@
export * from './api';
export * from './auth';
export * from './Cookies';
export * from './DeepPartial';
export * from './gens';
export * from './getAPIEnv';
export * from './parseSetCookie';
export * from './state';
export * from './url';
export * from './utils';

2
src/util/state/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './statetypes';
export * from './stateutils';

View File

@ -1,12 +1,4 @@
import { Dispatch, SetStateAction } from "react";
export type StateHook<T> = {
state: T;
setState: Dispatch<SetStateAction<T>>;
};
export type StateHookArray<T> = [
T,Dispatch<SetStateAction<T>>
]
import { StateHookArray, StateHook } from "./statetypes";
export function parseStateHook<T>(hook:StateHookArray<T>):StateHook<T>{
const stateHook:Partial<StateHook<T>> = {};

View 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>>
]

1
src/util/url/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./urlConstructor"

View File

@ -0,0 +1,12 @@
'use server'
import { getAPIEnv } from "../getAPIEnv";
export function constructAPIUrl(endpoint:string){
const { schema, host, port, basepath } = getAPIEnv();
return `${schema}://${host}:${port}/${basepath}/${endpoint}`
}
export function constructUrl(endpoint:string){
const { schema, host, port, basepath } = getAPIEnv();
return `${schema}://${host}:${port}/${endpoint}`
}1

View File

@ -11,7 +11,7 @@ import { EditorState } from "@/components/client/admin/PostEditor";
import { Project } from "@/models";
import { Dispatch, ReactNode, SetStateAction, useState } from "react";
import { Attributes } from "@sequelize/core";
import { parseStateHook, StateHookArray } from "@/util/state/stateUtils";
import { parseStateHook } from "@/util/state";
export type PostViewProps = {
children?: ReactNode;

View File

@ -1,7 +1,7 @@
cache: "no-store";
import { tryFetchPosts } from "@/app/api/post/route";
import { constructAPIUrl } from "@/util/Utils";
import { constructAPIUrl } from "@/util/utils";
import { ReactNode, useEffect } from "react";
import EntityManagementTable from "../../components/client/EntityManagementTable";
import PostTable from "@/components/client/admin/PostTable";