Working state for attachments and buckets

This commit is contained in:
Andreas Schaafsma 2024-06-12 18:08:08 +02:00
parent e84ce38604
commit c72ac5e67f
5 changed files with 156 additions and 56 deletions

View File

@ -4,19 +4,65 @@ 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 { UUID, randomUUID } from "crypto";
import { mkdir, mkdirSync, writeFile } from "fs";
import { Bucket } from "@/model/Bucket";
import { where } from "@sequelize/core";
import { PostBucket } from "@/model/Post";
async function writeFilesToBucket(uuid: UUID, files:any[]) {
const fileArray:{name:string, content:Promise<ReadableStreamReadResult<Uint8Array>>|ReadableStreamReadResult<Uint8Array>}[] = await Promise.all( files.map((file:File) => {
return {'name':( ()=>file.name)(), 'content':file.stream().getReader().read()};
}));
let finalFileArray:{name:string,content:ReadableStreamReadResult<Uint8Array>}[] = await (async () => {
for(let file in fileArray){
fileArray[file].content = await fileArray[file].content
}
return [...fileArray as {name:string,content:ReadableStreamReadResult<Uint8Array>}[]]
})() ;
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 attachment = await Attachment.create({bucket_id:uuid, filename:finalFileArray[file].name}, {include: Attachment.associations.bucket});
console.log(attachment);
}
}
async function addToPost(postid:number):Promise<UUID>
{
const post = await Post.findOne({where: {id:postid}, include: {association: Post.associations.postBuckets}});
if (!post) throw new APIError({ status: 500, responseText: "invalid postid" });
const bucket = await Bucket.create({id:randomUUID()});
const bucketPost = await PostBucket.create({bucketId: bucket.id, postId: postid})
console.log(bucketPost);
return bucket.id
}
async function addToBucket(bucketid:number):Promise<UUID> {
const bucket = await Bucket.findOne({where: {id: bucketid}});
if (!bucket) throw new APIError({ status: 500, responseText: "invalid bucketid" });
return bucket.id
}
async function tryCreateAttachment(request: Request) {
// Make sure the DB is ready
await Attachment.sync();
await Bucket.sync();
await Post.sync();
// 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
@ -39,46 +85,27 @@ async function tryCreateAttachment(request: Request) {
where: { token: authObject.token }
});
// Sanity check the auth and associated user
// Sanity check the auth and associated user for authorization
if (!auth || !auth.user) throw new APIError({ status: 401, responseText: "Authentication Error" });
// Handle incomplete data or other problems
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)}` });
// peepee
const uuid = randomUUID()
const fileArray:{name:string, content:Promise<ReadableStreamReadResult<Uint8Array>>|ReadableStreamReadResult<Uint8Array>}[] = await Promise.all( files.map((file:File) => {
return {'name':( ()=>file.name)(), 'content':file.stream().getReader().read()};
}));
// 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" });
let finalFileArray:{name:string,content:ReadableStreamReadResult<Uint8Array>}[] = await (async () => {
for(let file in fileArray){
fileArray[file].content = await fileArray[file].content
}
return [...fileArray as {name:string,content:ReadableStreamReadResult<Uint8Array>}[]]
})() ;
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)
const data = JSON.parse(requestData);
let uuid:UUID = (data.postid && !data.bucketid)? await addToPost(data.postid) : await addToBucket(data.bucketid)
writeFilesToBucket(uuid, files);
// console.log(await kanker[0]);
return new Response(JSON.stringify({
'files': fileArray,
'files': 'ya yeet',
'uuid': uuid,
}), { status: 200 });

View File

@ -6,11 +6,42 @@ import { APIError} from "@/util/api/error"
import { UserAuth, parseBasicAuth, getAssociatedUser } from "@/util/api/user"
import { Auth, Post, Tag, User } from "@/model/Models";
import { Project } from "@/model/Project";
import { Attachment } from "@/model/Attachment";
import { Bucket } from "@/model/Bucket";
import { DBState } from "@/model/DBState";
import Sequelize, { DataTypes } from "@sequelize/core";
import { SqliteColumnsDescription, SqliteDialect } from "@sequelize/sqlite3";
import { SqliteColumnsDescription, SqliteDialect, SqliteQueryInterface } from "@sequelize/sqlite3";
import { hashPassword } from "@/util/Auth";
async function seedDatabase(queryInterface:SqliteQueryInterface<SqliteDialect>){
const password = await hashPassword('changeme');
const project = await Project.findOne({where: {
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 />
this is some **test** markdown
`,
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){
const sequelize = await new Sequelize({
@ -21,27 +52,23 @@ async function trySetup(request:Request){
// await User.sync();
await Auth.sync();
await User.sync();
await Attachment.sync();
await Bucket.sync();
await Project.sync()
await Tag.sync();
await Post.sync();
await DBState.sync();
const version = await (await DBState.findAll()).sort((a,b)=> ((a.version > b.version) ? 1 : -1)).map(a=>a.version)[0];
const version = (await DBState.findAll()).sort((a,b)=> ((a.version > b.version) ? 1 : -1)).map(a=>a.version)[0];
await Project.findOne({where: {
readableIdentifier: 'blog'
}}).then(e=> e ? e : Project.create({name:'General Blog',readableIdentifier:'blog'}));
await User.findOne({where: {
username: 'admin'
}}).then(e=> e ? e : User.create({username: 'admin', password: 'changeme', perms: {isAdmin: true}}));
switch(version){
case 1:
break;
default:
const postsRows:SqliteColumnsDescription = await queryInterface.describeTable('Posts').then(t=>t);
seedDatabase(queryInterface);
const postsRows:SqliteColumnsDescription = await queryInterface.describeTable(Post.table.tableName).then(t=>t);
if (!postsRows['project_id']) queryInterface.addColumn('Posts','project_id',{type: DataTypes.INTEGER, acceptsNull:()=>false,defaultValue:1})
break;
}

View File

@ -1,26 +1,30 @@
import { Association, BelongsToGetAssociationMixin, BelongsToManyGetAssociationsMixin, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, NonAttribute, Sequelize } from "@sequelize/core";
import { Association, BelongsToGetAssociationMixin, BelongsToManyGetAssociationsMixin, CreationOptional, 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";
import { Bucket } from "./Bucket";
import { UUID } from "crypto";
export class Attachment extends Model<InferAttributes<Attachment>, InferCreationAttributes<Attachment>> {
@PrimaryKey
@AutoIncrement
@Attribute(DataTypes.INTEGER)
@Unique
declare id: number;
declare id: CreationOptional<number>;
@Attribute(DataTypes.STRING)
declare path: string
declare filename: string
// Associations
@Attribute(DataTypes.INTEGER)
@Attribute(DataTypes.UUIDV4)
@NotNull
declare postid: number;
@BelongsTo(()=>Post,{foreignKey: 'postid', inverse: {type: "hasMany", as: 'attachments'}})
declare post?:NonAttribute<Post>;
declare bucket_id: ForeignKey<Bucket['id']>;
@BelongsTo(()=>Bucket,{foreignKey: 'bucket_id', inverse: {type: "hasMany", as: 'attachments'}})
declare bucket?:NonAttribute<Bucket>;
declare static associations: {
posts: Association<Post, Attachment>;
bucket: Association<Bucket, Attachment>;
};
}

32
src/model/Bucket.ts Normal file
View File

@ -0,0 +1,32 @@
import { Association, BelongsToGetAssociationMixin, BelongsToManyGetAssociationsMixin, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, NonAttribute, Sequelize, sql } from "@sequelize/core";
import { Post } from "./Post";
import { SqliteDialect } from '@sequelize/sqlite3';
import { Attribute, AutoIncrement, BelongsTo, BelongsToMany, Default, NotNull, PrimaryKey, Unique } from "@sequelize/core/decorators-legacy";
import { Attachment } from "./Attachment";
import { UUID } from "crypto";
export class Bucket extends Model<InferAttributes<Bucket>, InferCreationAttributes<Bucket>> {
@PrimaryKey
@Unique
@Attribute(DataTypes.UUIDV4)
@Default(sql.uuidV4)
declare id: UUID
/** Defined by {@link Post.buckets} */
declare bucketPosts?:NonAttribute<Post>[];
/** Defined by {@link Attachment.bucket} */
declare attachments?:NonAttribute<Attachment>[];
declare static associations: {
bucketPosts: Association<Post, Bucket>;
attachments: Association<Attachment, Bucket>;
};
}
const sequelize = new Sequelize({
dialect: SqliteDialect,
storage: 'db.sqlite',
models: [Bucket]
})

View File

@ -1,4 +1,4 @@
import { Association, BelongsToGetAssociationMixin, CreationOptional, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, NonAttribute, Sequelize } from "@sequelize/core";import { User } from "./User";
import { Association, BelongsToGetAssociationMixin, BelongsToManyAssociation, BelongsToManyCreateAssociationMixin, CreationOptional, DataTypes, ForeignKey, InferAttributes, InferCreationAttributes, Model, NonAttribute, Sequelize } from "@sequelize/core";import { User } from "./User";
import { SqliteDialect } from '@sequelize/sqlite3';
import { Attribute, AutoIncrement, BelongsTo, BelongsToMany, CreatedAt, Default, HasMany, NotNull, PrimaryKey, Unique, UpdatedAt } from "@sequelize/core/decorators-legacy";
@ -6,6 +6,8 @@ import { Tag } from "./Tag";
import { PostTag } from "./PostTag";
import { Project } from "./Project";
import { Attachment } from "./Attachment";
import { Bucket } from "./Bucket";
import { UUID } from "crypto";
export class Post extends Model<InferAttributes<Post>, InferCreationAttributes<Post>> {
@ -47,21 +49,29 @@ export class Post extends Model<InferAttributes<Post>, InferCreationAttributes<P
@BelongsToMany(()=>Tag, { through: { model: ()=>PostTag, unique: false}, inverse: {as: 'taggedPosts'} })
declare postTags?:NonAttribute<Tag[]>;
/** Defined by {@link Attachment.post} */
declare Attachments:NonAttribute<Attachment>[];
@BelongsToMany(()=>Bucket,{through: { model: ()=> PostBucket, unique: false}, inverse:{as: 'bucketPosts'}, foreignKey: 'postId', otherKey: 'bucketId'})
declare postBuckets:NonAttribute<Bucket[]>;
declare getUser: BelongsToGetAssociationMixin<User>;
declare static associations: {
user: Association<User, Post>;
project: Association<Project, Post>;
postBuckets: Association<Bucket, Post>
postTags: Association<Tag, Post>;
};
}
export class PostBucket extends Model<InferAttributes<PostBucket>, InferCreationAttributes<PostBucket>>{
declare postId: number;
declare bucketId: UUID;
}
const sequelize = new Sequelize({
dialect: SqliteDialect,
storage: 'db.sqlite',
models: [Post]
})
models: [Post, PostBucket]
})
PostBucket.sync();