Skip to content

Commit

Permalink
Remove unstable multipart
Browse files Browse the repository at this point in the history
  • Loading branch information
Apsysikal committed Dec 23, 2024
1 parent aff373d commit dea0fa8
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 112 deletions.
8 changes: 2 additions & 6 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { HamburgerMenuIcon, InstagramLogoIcon } from "@radix-ui/react-icons";
import type {
LinksFunction,
LoaderFunctionArgs,
SerializeFrom,
} from "@remix-run/node";
import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
import { data } from "@remix-run/node";
import {
Form,
Expand Down Expand Up @@ -36,7 +32,7 @@ import { getToast } from "./utils/toast.server";
import stylesheet from "~/tailwind.css?url";
import { getUserWithRole } from "~/utils/session.server";

export type RootLoaderData = SerializeFrom<typeof loader>;
export type RootLoaderData = typeof loader;

export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
Expand Down
53 changes: 14 additions & 39 deletions app/routes/admin.dinners.$dinnerId_.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,25 @@ import {
useForm,
} from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { FileUpload, parseFormData } from "@mjackson/form-data-parser";
import {
ActionFunctionArgs,
LoaderFunctionArgs,
MaxPartSizeExceededError,
MetaFunction,
NodeOnDiskFile,
redirect,
unstable_composeUploadHandlers,
unstable_createFileUploadHandler,
unstable_createMemoryUploadHandler,
unstable_parseMultipartFormData,
} from "@remix-run/node";
import { Form, useActionData, useLoaderData } from "@remix-run/react";
import invariant from "tiny-invariant";
import { z } from "zod";

import { Field, SelectField, TextareaField } from "~/components/forms";
import { Button } from "~/components/ui/button";
import { prisma } from "~/db.server";
import { getAddresses } from "~/models/address.server";
import { getEventById, updateEvent } from "~/models/event.server";
import {
fileStorage,
getStorageKey,
} from "~/utils/dinner-image-storage.server";
import { ClientEventSchema } from "~/utils/event-validation";
import { ServerEventSchema } from "~/utils/event-validation.server";
import { getTimezoneOffset, offsetDate } from "~/utils/misc";
Expand Down Expand Up @@ -70,41 +68,18 @@ export async function action({ request, params }: ActionFunctionArgs) {
const { dinnerId } = params;
invariant(typeof dinnerId === "string", "Parameter dinnerId is missing");

const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
directory: process.env.IMAGE_UPLOAD_FOLDER,
}),
unstable_createMemoryUploadHandler(),
);
const uploadHandler = async (fileUpload: FileUpload) => {
let storageKey = getStorageKey("temporary-key");
await fileStorage.set(storageKey, fileUpload);
return fileStorage.get(storageKey);
};

const formData = await unstable_parseMultipartFormData(
request,
async (part) => {
try {
const result = await uploadHandler(part);
return result;
} catch (error) {
if (error instanceof MaxPartSizeExceededError) {
maximumFileSizeExceeded = true;
return new File([], "cover");
}
throw error;
}
},
);
const formData = await parseFormData(request, uploadHandler);

const submission = parseWithZod(formData, {
schema: (intent) =>
schema.superRefine((data, ctx) => {
schema.superRefine((data) => {
if (intent !== null) return { ...data };
if (maximumFileSizeExceeded) {
ctx.addIssue({
path: ["cover"],
code: z.ZodIssueCode.custom,
message: "File cannot be greater than 3MB",
});
return;
}
}),
});

Expand All @@ -115,7 +90,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
) {
// Remove the uploaded file from disk.
// It will be sent again when submitting.
await (submission.payload.cover as unknown as NodeOnDiskFile).remove();
await fileStorage.remove(getStorageKey("temporary-key"));
}

if (submission.status !== "success" || !submission.value) {
Expand All @@ -137,7 +112,7 @@ export async function action({ request, params }: ActionFunctionArgs) {

// Remove the file from disk.
// It is in the database now.
await (cover as NodeOnDiskFile).remove();
await fileStorage.remove(getStorageKey("temporary-key"));
}

const event = await updateEvent(dinnerId, {
Expand Down
55 changes: 16 additions & 39 deletions app/routes/admin.dinners.new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,24 @@ import {
useForm,
} from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { parseFormData, type FileUpload } from "@mjackson/form-data-parser";
import {
ActionFunctionArgs,
LoaderFunctionArgs,
MaxPartSizeExceededError,
MetaFunction,
NodeOnDiskFile,
redirect,
unstable_composeUploadHandlers,
unstable_createFileUploadHandler,
unstable_createMemoryUploadHandler,
unstable_parseMultipartFormData,
} from "@remix-run/node";
import { Form, useActionData, useLoaderData } from "@remix-run/react";
import { z } from "zod";

import { Field, SelectField, TextareaField } from "~/components/forms";
import { Button } from "~/components/ui/button";
import { prisma } from "~/db.server";
import { getAddresses } from "~/models/address.server";
import { createEvent } from "~/models/event.server";
import {
fileStorage,
getStorageKey,
} from "~/utils/dinner-image-storage.server";
import { ClientEventSchema } from "~/utils/event-validation";
import { ServerEventSchema } from "~/utils/event-validation.server";
import { getTimezoneOffset, offsetDate } from "~/utils/misc";
Expand Down Expand Up @@ -53,52 +51,31 @@ export async function action({ request }: ActionFunctionArgs) {
const timeOffset = getTimezoneOffset(request);
let maximumFileSizeExceeded = false;

const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
directory: process.env.IMAGE_UPLOAD_FOLDER,
}),
unstable_createMemoryUploadHandler(),
);
const uploadHandler = async (fileUpload: FileUpload) => {
let storageKey = getStorageKey("temporary-key");
await fileStorage.set(storageKey, fileUpload);
return fileStorage.get(storageKey);
};

const formData = await unstable_parseMultipartFormData(
request,
async (part) => {
try {
const result = await uploadHandler(part);
return result;
} catch (error) {
if (error instanceof MaxPartSizeExceededError) {
maximumFileSizeExceeded = true;
return new File([], "cover");
}
throw error;
}
},
);
const formData = await parseFormData(request, uploadHandler);

const submission = parseWithZod(formData, {
schema: (intent) =>
ServerEventSchema.superRefine((data, ctx) => {
ServerEventSchema.superRefine((data) => {
if (intent !== null) return { ...data };
if (maximumFileSizeExceeded) {
ctx.addIssue({
path: ["cover"],
code: z.ZodIssueCode.custom,
message: "File cannot be greater than 3MB",
});
return;
}
}),
});

console.log(submission.payload);

if (
submission.status !== "success" &&
submission.payload &&
submission.payload.cover
) {
// Remove the uploaded file from disk.
// It will be sent again when submitting.
await (submission.payload.cover as unknown as NodeOnDiskFile).remove();
await fileStorage.remove(getStorageKey("temporary-key"));
}

if (submission.status !== "success" || !submission.value) {
Expand Down Expand Up @@ -129,7 +106,7 @@ export async function action({ request }: ActionFunctionArgs) {

// Remove the file from disk.
// It is in the database now.
await (cover as NodeOnDiskFile).remove();
await fileStorage.remove(getStorageKey("temporary-key"));

return redirect(`/admin/dinners/${event.id}`);
}
Expand Down
8 changes: 2 additions & 6 deletions app/routes/admin.users._index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
LoaderFunctionArgs,
MetaFunction,
SerializeFrom,
} from "@remix-run/node";
import { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Link, useFetcher, useLoaderData } from "@remix-run/react";

import { Button } from "~/components/ui/button";
Expand Down Expand Up @@ -48,7 +44,7 @@ export default function DinnersPage() {
);
}

type User = Pick<SerializeFrom<typeof loader>, "users">["users"][number];
type User = Pick<Awaited<ReturnType<typeof loader>>, "users">["users"][number];

function User({ user }: { user: User }) {
const deleteFetcher = useFetcher();
Expand Down
16 changes: 16 additions & 0 deletions app/utils/dinner-image-storage.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { sep } from "node:path";

import { LocalFileStorage } from "@mjackson/file-storage/local";

const tmpDir = tmpdir();
const tmpPath = mkdtempSync(`${tmpDir}${sep}`);

export const fileStorage = new LocalFileStorage(
process.env.IMAGE_UPLOAD_FOLDER || tmpPath,
);

export function getStorageKey(id: string) {
return `dinner-${id}-cover`;
}
3 changes: 1 addition & 2 deletions app/utils/event-validation.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { NodeOnDiskFile } from "@remix-run/node";
import { z } from "zod";

export const ServerEventSchema = z.object({
Expand All @@ -14,7 +13,7 @@ export const ServerEventSchema = z.object({
.min(0, "Price cannot be less than 0")
.int(),
cover: z
.instanceof(NodeOnDiskFile, { message: "You must select a file" })
.instanceof(File, { message: "You must select a file" })
.refine((file) => {
return file.size !== 0;
}, "You must select a file")
Expand Down
66 changes: 47 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"@conform-to/react": "^1.2.2",
"@conform-to/zod": "^1.2.2",
"@headlessui/react": "^2.2.0",
"@mjackson/file-storage": "^0.3.0",
"@mjackson/form-data-parser": "^0.5.1",
"@prisma/client": "^6.1.0",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.4",
Expand All @@ -60,7 +62,6 @@
"devDependencies": {
"@eslint/js": "^9.17.0",
"@faker-js/faker": "^9.3.0",
"@react-router/remix-config-routes-adapter": "^0.0.0-nightly-bf7ecb711-20240911",
"@remix-run/dev": "^2.15.2",
"@remix-run/fs-routes": "^2.15.2",
"@remix-run/route-config": "^2.15.2",
Expand Down

0 comments on commit dea0fa8

Please sign in to comment.