diff --git a/README.md b/README.md index da6e2d9..a7dea1f 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ The database and UI are now connected, some improvements to make: Uploading a file to '[uploadthing](https://uploadthing.com/)' works, things that can be approved: -- [ ] Upload files to the right folder +- [x] Add "ownership" to files and folders +- [x] Upload files to the right folder - [ ] Delete file button -- [ ] Allow files that are not images to be uploaded +- [x] Allow files that are not images to be uploaded diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index 61a82aa..ba0baac 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -1,26 +1,35 @@ +/* eslint-disable @typescript-eslint/only-throw-error */ + import { auth } from "@clerk/nextjs/server"; import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; +import { z } from "zod"; import * as mutations from "~/server/db/mutations"; +import * as queries from "~/server/db/queries"; const f = createUploadthing(); export const ourFileRouter = { - imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } }) - .middleware(async () => { + driveUploader: f({ blob: { maxFileSize: "4MB", maxFileCount: 1 } }) + .input(z.object({ folderId: z.number() })) + .middleware(async ({ input }) => { const user = await auth(); - - // eslint-disable-next-line @typescript-eslint/only-throw-error if (!user.userId) throw new UploadThingError("Unauthorized"); - return { userId: user.userId }; + const folder = await queries.getFolderById(input.folderId); + if (!folder) throw new UploadThingError("Folder not found"); + + if (folder.ownerId !== user.userId) + throw new UploadThingError("Unauthorized"); + + return { userId: user.userId, parent: folder.id }; }) .onUploadComplete(async ({ metadata, file }) => { console.log("Upload complete for userId:", metadata.userId); console.log("file url", file.ufsUrl); - const uploadedFile = { ...file, parent: 0 }; + const uploadedFile = { ...file, parent: metadata.parent }; await mutations.createFile(uploadedFile, metadata.userId); return { uploadedBy: metadata.userId }; diff --git a/src/app/drive-contents.tsx b/src/app/drive-contents.tsx index f5a8149..97636d8 100644 --- a/src/app/drive-contents.tsx +++ b/src/app/drive-contents.tsx @@ -13,6 +13,7 @@ export default function DriveContents(props: { files: File[]; folders: Folder[]; parents: Folder[]; + currentFolderId: number; }) { const navigate = useRouter(); @@ -64,7 +65,8 @@ export default function DriveContents(props: { { navigate.refresh(); }} diff --git a/src/app/f/[folderId]/page.tsx b/src/app/f/[folderId]/page.tsx index 5c1a3b6..f788a9b 100644 --- a/src/app/f/[folderId]/page.tsx +++ b/src/app/f/[folderId]/page.tsx @@ -20,5 +20,12 @@ export default async function GoogleDriveClone(props: { queries.getAllParentsForFolder(data.folderId), ]); - return ; + return ( + + ); } diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts index ba2e9a2..50f1e5b 100644 --- a/src/lib/mock-data.ts +++ b/src/lib/mock-data.ts @@ -16,7 +16,6 @@ export type Folder = { // prettier-ignore export const mockFolders: Folder[] = [ - { id: "root", name: "root", type: "folder", parent: null }, // the root folder { id: "1", name: "Documents", type: "folder", parent: "root" }, { id: "2", name: "Images", type: "folder", parent: "root" }, { id: "3", name: "Work", type: "folder", parent: "root" }, diff --git a/src/server/db/mutations.ts b/src/server/db/mutations.ts index d28a28e..bfedb51 100644 --- a/src/server/db/mutations.ts +++ b/src/server/db/mutations.ts @@ -1,6 +1,9 @@ import { db } from "~/server/db"; import { type File, files_table as filesSchema } from "~/server/db/schema"; -export function createFile(file: Omit, _userId: string) { - return db.insert(filesSchema).values({ ...file, parent: 1 }); +export function createFile( + file: Pick, + userId: string, +) { + return db.insert(filesSchema).values({ ...file, ownerId: userId }); } diff --git a/src/server/db/queries.ts b/src/server/db/queries.ts index 4ba47f7..e20fd24 100644 --- a/src/server/db/queries.ts +++ b/src/server/db/queries.ts @@ -33,6 +33,14 @@ export function getAllFolders(folderId: number) { .where(eq(folderSchema.parent, folderId)); } +export async function getFolderById(folderId: number) { + const folders = await db + .select() + .from(folderSchema) + .where(eq(folderSchema.id, folderId)); + return folders[0]; +} + export function getAllFiles(folderId: number) { return db.select().from(fileSchema).where(eq(fileSchema.parent, folderId)); } diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 9a67b4e..7745fda 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -4,6 +4,7 @@ import { text, singlestoreTableCreator, bigint, + timestamp, } from "drizzle-orm/singlestore-core"; const createTable = singlestoreTableCreator((name) => `drive_tutorial_${name}`); @@ -14,12 +15,17 @@ export const files_table = createTable( id: bigint("id", { mode: "number", unsigned: true }) .primaryKey() .autoincrement(), + ownerId: text("owner_id").notNull(), name: text("name").notNull(), url: text("url").notNull(), size: int("size").notNull(), - parent: bigint("parent", { mode: "number", unsigned: true }), + parent: bigint("parent", { mode: "number", unsigned: true }).notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), }, - (table) => [index("parent_index").on(table.parent)], + (table) => [ + index("owner_id_index").on(table.ownerId), + index("parent_index").on(table.parent), + ], ); export const folders_table = createTable( @@ -28,10 +34,15 @@ export const folders_table = createTable( id: bigint("id", { mode: "number", unsigned: true }) .primaryKey() .autoincrement(), + ownerId: text("owner_id").notNull(), name: text("name").notNull(), parent: bigint("parent", { mode: "number", unsigned: true }), + createdAt: timestamp("created_at").notNull().defaultNow(), }, - (table) => [index("parent_index").on(table.parent)], + (table) => [ + index("owner_id_index").on(table.ownerId), + index("parent_index").on(table.parent), + ], ); export type File = typeof files_table.$inferSelect;