Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@

- [x] Add ownership to files
- [x] Upload fiiles to the correct folder
- [ ] Delete files button
- [x] Delete files button
- [x] Allow files thay arent images to be uploaded
- [x] Display file type and file sizes correctly in the view
- [ ] Retrieve the file type from uploadthing and save it in db
- [ ] real homepage
- [x] Retrieve the file type from uploadthing and save it in db
- [x] real homepage

## notes 2:40.44
- [ ] Real homepage + onboarding

## followup

[ ] folder deletion - make sure to recursively delete all files and folders.
[ ] folder deletion - make sure to recursively delete all files and folders
52 changes: 52 additions & 0 deletions src/app/(home)/drive/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
import { Button } from "~/components/ui/button";
import { MUTATIONS, QUERIES } from "~/server/db/queries";

export default async function DrivePage() {
const session = await auth();
if (!session.userId) {
return redirect("/sign-in");
}

const rootFolder = await QUERIES.getRootFolderForUser(session.userId);

if (typeof rootFolder === "object" && "error" in rootFolder) {
// Handle error case, e.g., show a message to the user
console.error("Error fetching root folder:", rootFolder.error);
return (
<>
<div>Error fetching root folder</div>
</>
);
}

if (!rootFolder) {
// create the root folder for the user
return (
<>
<form
action={async () => {
"use server";
const session = await auth();
if (!session.userId) {
return redirect("/sign-in");
}

const rootFolderId = await MUTATIONS.onboardUser(session.userId);
if (typeof rootFolderId === "object" && "error" in rootFolderId) {
// Handle error case, e.g., show a message to the user
console.error("Error creating root folder:", rootFolderId.error);
return;
}
return redirect(`/folder/${rootFolderId}`);
}}
>
<Button> Create New Drive</Button>
</form>
</>
);
}

return redirect(`/folder/${rootFolder.id}`);
}
14 changes: 14 additions & 0 deletions src/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
import { Button } from "~/components/ui/button";

export default function HomeLayout(props: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-br from-black via-neutral-900 to-neutral-800 p-4 text-white">
<main className="text-center">{props.children}</main>
<footer className="mt-16 text-sm text-neutral-500">
© {new Date().getFullYear()} T3 Drive. All rights reserved.
</footer>
</div>
);
}
41 changes: 35 additions & 6 deletions src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
import { Button } from "~/components/ui/button";

export default function HomePage() {
return (
<>
<h1 className="mb-4 bg-gradient-to-r from-neutral-200 to-neutral-400 bg-clip-text text-5xl font-bold text-transparent md:text-6xl">
M8 Drive
</h1>
<p className="mx-auto mb-8 max-w-md text-xl text-neutral-400 md:text-2xl">
Secure, fast, and easy file storage for the modern web
</p>
<form
action={async () => {
"use server";

export default function GoogleDriveClone() {
// const files = await db.select().from(fileSchema);
// const folders = await db.select().from(foldersSchema);
// return <DriveContents files={files} folders={folders} />;
return <div className="">Hello World</div>
}
const session = await auth();

if (!session.userId) {
return redirect("/sign-in");
}

return redirect("/drive");
}}
>
<Button
size="lg"
type="submit"
className="border border-neutral-700 bg-neutral-800 text-white transition-colors hover:bg-neutral-700"
>
Get Started
</Button>
</form>
</>
);
}
9 changes: 9 additions & 0 deletions src/app/(home)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SignInButton } from "@clerk/nextjs";

export default function SignInPage() {
return (
<>
<SignInButton forceRedirectUrl={"/drive"} />
</>
);
}
184 changes: 135 additions & 49 deletions src/server/db/queries.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,142 @@
import 'server-only';
import "server-only";

import { eq } from "drizzle-orm";
import { eq, and, isNull } from "drizzle-orm";
import { db } from ".";
import { DB_FileType, files_table as fileSchema, folders_table as foldersSchema } from "~/server/db/schema"
import {
DB_FileType,
files_table as fileSchema,
folders_table as foldersSchema,
} from "~/server/db/schema";

export const QUERIES = {
getAllParentsForFolder: async function (folderId: number) {
const parents = [];
let currentId: number | null = folderId;
while (currentId !== null && currentId !== 1) {
const folder = await db
.selectDistinct()
.from(foldersSchema)
.where(eq(foldersSchema.id, currentId));

if (!folder[0]) {
throw new Error(`Folder with ID ${currentId} not found`);
}
parents.unshift(folder[0]);
currentId = folder[0]?.parent ?? null;
}
return parents;
},

getFiles: async function (folderId: number) {
const filePromise = await db.select().from(fileSchema).where(eq(fileSchema.parent, folderId)).orderBy(fileSchema.createdAt);
return filePromise
},

getFolders: async function (folderId: number) {
const foldersPromise = await db.select().from(foldersSchema).where(eq(foldersSchema.parent, folderId)).orderBy(foldersSchema.createdAt);
return foldersPromise
},
getFolderById: async function (folderId: number) {
const folder = await db.select().from(foldersSchema).where(eq(foldersSchema.id, folderId));
if (!folder[0]) {
throw new Error(`Folder with ID ${folderId} not found`);
}
return folder[0];
getAllParentsForFolder: async function (folderId: number) {
const parents = [];
let currentId: number | null = folderId;
while (currentId !== null && currentId !== 1) {
const folder = await db
.selectDistinct()
.from(foldersSchema)
.where(eq(foldersSchema.id, currentId));

if (!folder[0]) {
throw new Error(`Folder with ID ${currentId} not found`);
}
parents.unshift(folder[0]);
currentId = folder[0]?.parent ?? null;
}
return parents;
},

getFiles: async function (folderId: number) {
const filePromise = await db
.select()
.from(fileSchema)
.where(eq(fileSchema.parent, folderId))
.orderBy(fileSchema.createdAt);
return filePromise;
},

getFolders: async function (folderId: number) {
const foldersPromise = await db
.select()
.from(foldersSchema)
.where(eq(foldersSchema.parent, folderId))
.orderBy(foldersSchema.createdAt);
return foldersPromise;
},
getFolderById: async function (folderId: number) {
const folder = await db
.select()
.from(foldersSchema)
.where(eq(foldersSchema.id, folderId));
if (!folder[0]) {
throw new Error(`Folder with ID ${folderId} not found`);
}
return folder[0];
},

getRootFolderForUser: async function (userId: string) {
// get the root folder for the user
if (!userId) {
return { error: "Unauthorized" };
}
}
const rootFolder = await db
.select()
.from(foldersSchema)
.where(
and(eq(foldersSchema.ownerId, userId), isNull(foldersSchema.parent)),
);
return rootFolder[0];
},
};

export const MUTATIONS = {
createFile: async function (input : {
file: {
name: string;
size: number;
url: string;
parent: number;
ownerId: string;
}, userId: string}) {
if (!input.userId) throw new Error("Unauthorized");
return await db.insert(fileSchema).values(input.file)
},
}
createFile: async function (input: {
file: {
name: string;
size: number;
url: string;
parent: number;
ownerId: string;
};
userId: string;
}) {
if (!input.userId) throw new Error("Unauthorized");
return await db.insert(fileSchema).values(input.file);
},

createFolder: async function (input: {
name: string;
parentId: number | null;
userId: string;
}) {
const { name, parentId, userId } = input;
if (!userId) throw new Error("Unauthorized");
return await db.insert(foldersSchema).values({
name,
parent: parentId,
ownerId: userId,
});
},

onboardUser: async function (userId: string) {
const isExistingUser = await db
.select()
.from(foldersSchema)
.where(eq(foldersSchema.ownerId, userId))
.limit(1);
if (isExistingUser[0]) {
return { error: "User already exists" };
}
const rootFolder = await db
.insert(foldersSchema)
.values({
name: "Root",
parent: null,
ownerId: userId,
})
.$returningId();

const rootFolderId = rootFolder[0]!.id;

await db.insert(foldersSchema).values([
{
name: "Shared",
parent: rootFolderId,
ownerId: userId,
},
{
name: "Favourites",
parent: rootFolderId,
ownerId: userId,
},
{
name: "Documents",
parent: rootFolderId,
ownerId: userId,
},
]);

return rootFolderId;
},
};