Skip to content

Commit

Permalink
add file renaming for admins
Browse files Browse the repository at this point in the history
  • Loading branch information
RealFascinated committed Mar 7, 2025
1 parent 1b02c75 commit 5a317ad
Show file tree
Hide file tree
Showing 29 changed files with 390 additions and 115 deletions.
58 changes: 15 additions & 43 deletions drizzle/meta/0005_snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,8 @@
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
Expand Down Expand Up @@ -174,12 +170,8 @@
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
Expand All @@ -189,9 +181,7 @@
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
"columns": ["token"]
}
},
"policies": {},
Expand Down Expand Up @@ -294,16 +284,12 @@
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
},
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
"columns": ["username"]
}
},
"policies": {},
Expand Down Expand Up @@ -431,12 +417,8 @@
"name": "file_user_id_users_id_fk",
"tableFrom": "file",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
Expand Down Expand Up @@ -489,12 +471,8 @@
"name": "preferences_user_id_users_id_fk",
"tableFrom": "preferences",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
Expand All @@ -504,9 +482,7 @@
"preferences_user_id_unique": {
"name": "preferences_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
"columns": ["user_id"]
}
},
"policies": {},
Expand Down Expand Up @@ -548,12 +524,8 @@
"name": "thumbnail_user_id_users_id_fk",
"tableFrom": "thumbnail",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
Expand All @@ -576,4 +548,4 @@
"schemas": {},
"tables": {}
}
}
}
2 changes: 1 addition & 1 deletion drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@
"breakpoints": true
}
]
}
}
2 changes: 1 addition & 1 deletion src/app/(pages)/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default async function DashboardPage() {
<WelcomeBanner username={user.name} />

<UserStatistics />
<UserFiles />
<UserFiles user={user} />
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/api/upload/sharex/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export async function POST(
}

// Validate file types
if (!files.every((file) => file instanceof File)) {
if (!files.every(file => file instanceof File)) {
return NextResponse.json(
{ message: "Invalid file format" },
{ status: 400 }
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/user/config/sharex/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { env } from "@/lib/env";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest): Promise<NextResponse> {
return handleApiRequestWithUser(async (user) => {
return handleApiRequestWithUser(async user => {
if (!user.uploadToken) {
return NextResponse.json(
{
Expand Down
6 changes: 4 additions & 2 deletions src/app/api/user/file/delete/[key]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getUserById } from "@/lib/helpers/user";
import Logger from "@/lib/logger";
import { Notifications } from "@/lib/notification";
import { getFileName } from "@/lib/utils/file";
import { getFileThumbnailPath } from "@/lib/utils/paths";
import { getFilePath, getFileThumbnailPath } from "@/lib/utils/paths";
import { storage } from "@/storage/create-storage";
import { NextRequest, NextResponse } from "next/server";

Expand All @@ -25,7 +25,9 @@ export async function GET(
}

try {
const deletedFile = await storage.deleteFile(getFileName(file));
const deletedFile = await storage.deleteFile(
getFilePath(file.userId, file)
);
if (!deletedFile) {
return NextResponse.json(
{ message: "Unable to delete the file, please contact an admin" },
Expand Down
87 changes: 87 additions & 0 deletions src/app/api/user/file/rename/[key]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { handleApiRequestWithUser, notFound } from "@/lib/api-commons";
import { FileType } from "@/lib/db/schemas/file";
import { ThumbnailType } from "@/lib/db/schemas/thumbnail";
import { env } from "@/lib/env";
import { getFileById, updateFile } from "@/lib/helpers/file";
import { getUserRole, roles } from "@/lib/helpers/role";
import { getThumbnailById, updateThumbnail } from "@/lib/helpers/thumbnail";
import Logger from "@/lib/logger";
import { getFileName } from "@/lib/utils/file";
import { getFilePath, getFileThumbnailPath } from "@/lib/utils/paths";
import { storage } from "@/storage/create-storage";
import { NextRequest, NextResponse } from "next/server";

export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ key: string }> }
): Promise<NextResponse> {
return handleApiRequestWithUser(async user => {
if (getUserRole(user) !== roles.admin) {
return NextResponse.json(
{ message: "You do not have permission to rename this file." },
{ status: 403 }
);
}

const { key } = await params;
const file = await getFileById(key);
if (!file) {
return notFound;
}

const { id } = await request.json();
if (!id) {
return NextResponse.json(
{ message: "The id is required." },
{ status: 400 }
);
}

if (id.length > env.FILE_ID_LENGTH) {
return NextResponse.json(
{ message: "The id is too long." },
{ status: 400 }
);
}

if ((await getFileById(id)) !== undefined) {
return NextResponse.json(
{ message: "The id is already taken." },
{ status: 400 }
);
}

// Rename the file
await updateFile(file.id, { id: id });
await storage.renameFile(
getFilePath(file.userId, file),
getFilePath(file.userId, {
...file,
id: id,
} as FileType)
);

// Rename the thumbnail if it exists
if (file.hasThumbnail) {
const thumbnail = await getThumbnailById(file.id);
await storage.renameFile(
getFileThumbnailPath(file.userId, thumbnail),
getFileThumbnailPath(file.userId, {
...thumbnail,
id: id,
} as ThumbnailType)
);
await updateThumbnail(file.id, { id: id });
}
Logger.info(
`The file ${getFileName(file)} had its id changed to ${id}.${file.extension} by ${user.username}`
);

return NextResponse.json(
{
message: "Successfully renamed",
},
{ status: 200 }
);
});
}
4 changes: 2 additions & 2 deletions src/app/api/user/files/[page]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function GET(
params: Promise<{ page: string }>;
}
): Promise<NextResponse | Response> {
return handleApiRequestWithUser(async (user) => {
return handleApiRequestWithUser(async user => {
const { page } = await params;

const searchParams = request.nextUrl.searchParams;
Expand All @@ -32,7 +32,7 @@ export async function GET(

const paginatedPage = await pagination.getPage(
Number(page),
async (fetchItems) => {
async fetchItems => {
const files = await getUserFiles(user.id, {
limit: ITEMS_PER_PAGE,
offset: fetchItems.start,
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/user/reset-upload-token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest): Promise<NextResponse> {
return handleApiRequestWithUser(async (user) => {
return handleApiRequestWithUser(async user => {
const requestHeaders = await headers();
try {
await auth.api.updateUser({
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/user/statistics/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UserStatisticsResponse } from "@/type/api/user/statistics-response";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest): Promise<NextResponse> {
return handleApiRequestWithUser(async (user) => {
return handleApiRequestWithUser(async user => {
const images = await getUserFiles(user.id);
const thumbnails = await getUserThumbnails(user.id);

Expand Down
2 changes: 1 addition & 1 deletion src/app/api/user/update-preference/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { updateUserPreferences } from "@/lib/preference";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest): Promise<NextResponse> {
return handleApiRequestWithUser(async (user) => {
return handleApiRequestWithUser(async user => {
const { showKitty, webhookUrl, notifications } = await request.json();
await updateUserPreferences(user.id, {
showKitty,
Expand Down
4 changes: 2 additions & 2 deletions src/components/auth/create-account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function CreateAccount() {
router.push("/dashboard");
router.refresh();
},
onError: (error) => {
onError: error => {
console.log({ error });
setError(error.error.message);
},
Expand Down Expand Up @@ -121,7 +121,7 @@ export default function CreateAccount() {
<Input
placeholder="username"
{...field}
onChange={(event) => {
onChange={event => {
event.target.value =
event.target.value.toLowerCase();
field.onChange(event);
Expand Down
29 changes: 5 additions & 24 deletions src/components/dashboard/user/files/delete-file-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { FileType } from "@/lib/db/schemas/file";
import request from "@/lib/request";
import { getFileName } from "@/lib/utils/file";
import { cloneElement, ReactElement, useState } from "react";
import { ReactElement } from "react";
import { toast } from "sonner";

type DeleteFileDialogProps = UserFileProps & {
children: ReactElement<{ onClick?: (e: React.MouseEvent) => void }>;
children: ReactElement;
};

export default function DeleteFileDialog({
fileMeta,
refetch,
children,
}: DeleteFileDialogProps) {
const [deleteConfirm, setDeleteConfirm] = useState<boolean>(false);

/**
* Deletes a file
*
Expand All @@ -42,29 +41,11 @@ export default function DeleteFileDialog({
} catch {
toast(`Failed to delete ${getFileName(fileMeta)}`);
}
setDeleteConfirm(false);
}

// Create a modified version of children that opens the dialog when clicked
const triggerElement = cloneElement(children, {
onClick: (e: React.MouseEvent) => {
// Prevent default behavior
e.preventDefault();
// Stop propagation to prevent context menu from closing
e.stopPropagation();
// Open the dialog
setDeleteConfirm(true);

// Call the original onClick if it exists
if (children.props.onClick) {
children.props.onClick(e);
}
},
});

return (
<Dialog open={deleteConfirm} onOpenChange={setDeleteConfirm}>
{triggerElement}
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
Expand Down
Loading

0 comments on commit 5a317ad

Please sign in to comment.