Skip to content

Commit

Permalink
Merge pull request #1584 from rockingrohit9639/fix/update-import-head…
Browse files Browse the repository at this point in the history
…er-error-message

fix(import-header-error): add zod validation and showing proper error message for invalid data
  • Loading branch information
DonKoko authored Jan 17, 2025
2 parents aa0f246 + 7d2327e commit 731d508
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 17 deletions.
18 changes: 6 additions & 12 deletions app/modules/asset/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import type {
CustomFieldType,
} from "@prisma/client";
import type { Return } from "@prisma/client/runtime/library";
import type { z } from "zod";
import type { assetIndexFields } from "./fields";
import type { importAssetsSchema } from "./utils.server";

export interface ICustomFieldValueJson {
raw: string | number | boolean;
Expand Down Expand Up @@ -44,18 +46,10 @@ export interface UpdateAssetPayload {
valuation?: Asset["valuation"];
}

export interface CreateAssetFromContentImportPayload
extends Record<string, any> {
title: string;
description?: string;
category?: string;
kit?: string;
tags: string[];
location?: string;
custodian?: string;
bookable?: "yes" | "no";
imageUrl?: string; // URL of the image to import
}
export type CreateAssetFromContentImportPayload = z.infer<
typeof importAssetsSchema
>;

export interface CreateAssetFromBackupImportPayload
extends Record<string, any> {
id: string;
Expand Down
14 changes: 14 additions & 0 deletions app/modules/asset/utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,17 @@ export function validateAdvancedFilterParams(

return validatedParams;
}

export const importAssetsSchema = z
.object({
title: z.string(),
description: z.string().optional(),
category: z.string().optional(),
kit: z.string().optional(),
tags: z.string().array(),
location: z.string().optional(),
custodian: z.string().optional(),
bookable: z.enum(["yes", "no"]).optional(),
imageUrl: z.string().url().optional(),
})
.and(z.record(z.string(), z.any()));
7 changes: 6 additions & 1 deletion app/routes/_layout+/admin-dashboard+/org.$organizationId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import HorizontalTabs from "~/components/layout/horizontal-tabs";
import { Button } from "~/components/shared/button";
import { db } from "~/database/db.server";
import { createAssetsFromContentImport } from "~/modules/asset/service.server";
import { importAssetsSchema } from "~/modules/asset/utils.server";
import { toggleOrganizationSso } from "~/modules/organization/service.server";
import { csvDataFromRequest } from "~/utils/csv.server";
import { ShelfError, makeShelfError } from "~/utils/error";
Expand Down Expand Up @@ -154,7 +155,11 @@ export const action = async ({
label: "Assets",
});
}
const contentData = extractCSVDataFromContentImport(csvData);

const contentData = extractCSVDataFromContentImport(
csvData,
importAssetsSchema.array()
);
await createAssetsFromContentImport({
data: contentData,
userId,
Expand Down
8 changes: 6 additions & 2 deletions app/routes/_layout+/assets.import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
TabsTrigger,
} from "~/components/shared/tabs";
import { createAssetsFromContentImport } from "~/modules/asset/service.server";
import { importAssetsSchema } from "~/modules/asset/utils.server";
import { appendToMetaTitle } from "~/utils/append-to-meta-title";
import { csvDataFromRequest } from "~/utils/csv.server";
import { ShelfError, makeShelfError } from "~/utils/error";
Expand Down Expand Up @@ -52,7 +53,6 @@ export const action = async ({ context, request }: ActionFunctionArgs) => {
);

const csvData = await csvDataFromRequest({ request });

if (csvData.length < 2) {
throw new ShelfError({
cause: null,
Expand All @@ -63,7 +63,11 @@ export const action = async ({ context, request }: ActionFunctionArgs) => {
});
}

const contentData = extractCSVDataFromContentImport(csvData);
const contentData = extractCSVDataFromContentImport(
csvData,
importAssetsSchema.array()
);

await createAssetsFromContentImport({
data: contentData,
userId,
Expand Down
21 changes: 19 additions & 2 deletions app/utils/import.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { ZodSchema } from "zod";
import type { CreateAssetFromContentImportPayload } from "~/modules/asset/types";
import { ShelfError } from "./error";

/* This function receives an array of object and a key name
* It then extracts all the values of that key and makes sure there are no duplicates
Expand All @@ -25,15 +27,18 @@ export function getUniqueValuesFromArrayOfObjects({
}

/** Takes the CSV data from a `content` import and parses it into an object that we can then use to create the entries */
export function extractCSVDataFromContentImport(data: string[][]) {
export function extractCSVDataFromContentImport<Schema extends ZodSchema>(
data: string[][],
schema: Schema
) {
/**
* The first row of the CSV contains the keys for the data
* We need to trim the keys to remove any whitespace and special characters and Non-printable characters as it already causes issues with in the past
* Non-printable character: The non-printable character you encountered at the beginning of the title property key ('\ufeff') is known as the Unicode BOM (Byte Order Mark).
*/
const keys = data[0].map((key) => key.trim()); // Trim the keys
const values = data.slice(1) as string[][];
return values.map((entry) =>
const rawData = values.map((entry) =>
Object.fromEntries(
entry.map((value, index) => {
switch (keys[index]) {
Expand All @@ -48,6 +53,18 @@ export function extractCSVDataFromContentImport(data: string[][]) {
})
)
);

const parsedResult = schema.safeParse(rawData);
if (!parsedResult.success) {
throw new ShelfError({
cause: null,
message:
"Received invalid data, please update the file with proper headers and data.",
label: "Assets",
});
}

return parsedResult.data as Schema["_output"];
}

/** Takes the CSV data from a `backup` import and parses it into an object that we can then use to create the entries */
Expand Down

0 comments on commit 731d508

Please sign in to comment.