Skip to content

Commit

Permalink
Merge pull request #93 from complexdatacollective/fix/unique-assetId
Browse files Browse the repository at this point in the history
fix: assetId not unique
  • Loading branch information
jthrilly committed Mar 26, 2024
2 parents 67d5789 + 2ab8efd commit 67234db
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 17 deletions.
46 changes: 38 additions & 8 deletions hooks/useProtocolImport.tsx
Expand Up @@ -37,6 +37,8 @@ export const useProtocolImport = () => {
const { mutateAsync: getProtocolExists } =
api.protocol.get.byHash.useMutation();

const { mutateAsync: getNewAssetIds } = api.asset.checkExisting.useMutation();

/**
* This is the main job processing function. Takes a file, and handles all
* the steps required to import it into the database, updating the job
Expand Down Expand Up @@ -160,9 +162,36 @@ export const useProtocolImport = () => {
}

const assets = await getProtocolAssets(protocolJson, zip);
let assetsWithCombinedMetadata: z.infer<typeof assetInsertSchema> = [];

if (assets.length > 0) {
const newAssets: typeof assets = [];

const existingAssetIds: string[] = [];

let newAssetsWithCombinedMetadata: z.infer<typeof assetInsertSchema> = [];

// Check if the assets are already in the database.
// If yes, add them to existingAssetIds to be connected to the protocol.
// If not, add them to newAssets to be uploaded.

try {
const newAssetIds = await getNewAssetIds(
assets.map((asset) => asset.assetId),
);

assets.forEach((asset) => {
if (newAssetIds.includes(asset.assetId)) {
newAssets.push(asset);
} else {
existingAssetIds.push(asset.assetId);
}
});
} catch (e) {
throw new Error('Error checking for existing assets');
}

// Upload the new assets

if (newAssets.length > 0) {
dispatch({
type: 'UPDATE_STATUS',
payload: {
Expand All @@ -177,18 +206,18 @@ export const useProtocolImport = () => {
* track the current bytes uploaded per file (uploads are done in
* parallel).
*/
const totalBytesToUpload = assets.reduce((acc, asset) => {
const totalBytesToUpload = newAssets.reduce((acc, asset) => {
return acc + asset.file.size;
}, 0);

const currentBytesUploaded: Record<string, number> = {};

const files = assets.map((asset) => asset.file);
const files = newAssets.map((asset) => asset.file);

const uploadedFiles = await uploadFiles('assetRouter', {
files,
onUploadProgress({ progress, file }) {
const thisFileSize = assets.find((asset) => asset.name === file)!
const thisFileSize = newAssets.find((asset) => asset.name === file)!
.file.size; // eg. 1000

const thisCompletedBytes = thisFileSize * (progress / 100);
Expand Down Expand Up @@ -222,11 +251,11 @@ export const useProtocolImport = () => {
/**
* We now need to merge the metadata from the uploaded files with the
* asset metadata from the protocol json, so that we can insert the
* assets into the database.
* newassets into the database.
*
* The 'name' prop matches across both - we can use that to merge them.
*/
assetsWithCombinedMetadata = assets.map((asset) => {
newAssetsWithCombinedMetadata = newAssets.map((asset) => {
const uploadedAsset = uploadedFiles.find(
(uploadedFile) => uploadedFile.name === asset.name,
);
Expand Down Expand Up @@ -259,7 +288,8 @@ export const useProtocolImport = () => {
const result = await insertProtocol({
protocol: protocolJson,
protocolName: fileName,
assets: assetsWithCombinedMetadata,
newAssets: newAssetsWithCombinedMetadata,
existingAssetIds: existingAssetIds,
});

if (result.error) {
Expand Down
5 changes: 2 additions & 3 deletions prisma/schema.prisma
Expand Up @@ -33,10 +33,9 @@ model Asset {
type String
url String
size Int
protocol Protocol @relation(fields: [protocolId], references: [id], onDelete: Cascade)
protocolId String // from db
protocols Protocol[]
@@index(fields: [protocolId, assetId, key])
@@index(fields: [assetId, key])
}

model Interview {
Expand Down
2 changes: 2 additions & 0 deletions server/router.ts
Expand Up @@ -5,9 +5,11 @@ import { protocolRouter } from '~/server/routers/protocol';
import { participantRouter } from './routers/participant';
import { router } from './trpc';
import { dashboardRouter } from './routers/dashboard';
import { assetRouter } from './routers/asset';

export const appRouter = router({
appSettings: appSettingsRouter,
asset: assetRouter,
dashboard: dashboardRouter,
session: sessionRouter,
interview: interviewRouter,
Expand Down
21 changes: 21 additions & 0 deletions server/routers/asset.ts
@@ -0,0 +1,21 @@
/* eslint-disable local-rules/require-data-mapper */
import { prisma } from '~/utils/db';
import { protectedProcedure, router } from '~/server/trpc';
import { z } from 'zod';

export const assetRouter = router({
checkExisting: protectedProcedure
.input(z.array(z.string()))
.mutation(async ({ input: assetIds }) => {
const assets = await prisma.asset.findMany({
where: {
assetId: {
in: assetIds,
},
},
});
const existingAssets = assets.map((asset) => asset.assetId);
// Return the assetIds that are not in the database
return assetIds.filter((assetId) => !existingAssets.includes(assetId));
}),
});
28 changes: 22 additions & 6 deletions server/routers/protocol.ts
Expand Up @@ -27,8 +27,17 @@ export const deleteProtocols = async (hashes: string[]) => {
select: { id: true, name: true },
});

// Select assets that are ONLY associated with the protocols to be deleted
const assets = await prisma.asset.findMany({
where: { protocolId: { in: protocolsToBeDeleted.map((p) => p.id) } },
where: {
protocols: {
every: {
id: {
in: protocolsToBeDeleted.map((p) => p.id),
},
},
},
},
select: { key: true },
});
// We put asset deletion in a separate try/catch because if it fails, we still
Expand Down Expand Up @@ -145,13 +154,19 @@ export const protocolRouter = router({
.object({
protocol: z.unknown(), // TODO: replace this with zod schema version of Protocol type
protocolName: z.string(),
assets: assetInsertSchema,
newAssets: assetInsertSchema,
existingAssetIds: z.array(z.string()),
})
.passthrough()
.parse(value);
})
.mutation(async ({ input }) => {
const { protocol: inputProtocol, protocolName, assets } = input;
const {
protocol: inputProtocol,
protocolName,
newAssets,
existingAssetIds,
} = input;

const protocol = inputProtocol as Protocol;

Expand All @@ -169,7 +184,8 @@ export const protocolRouter = router({
codebook: protocol.codebook,
description: protocol.description,
assets: {
create: assets,
create: newAssets,
connect: existingAssetIds.map((assetId) => ({ assetId })),
},
},
});
Expand All @@ -189,8 +205,8 @@ export const protocolRouter = router({
return { error: null, success: true };
} catch (e) {
// Attempt to delete any assets we uploaded to storage
if (assets.length > 0) {
await deleteFilesFromUploadThing(assets.map((a) => a.key));
if (newAssets.length > 0) {
await deleteFilesFromUploadThing(newAssets.map((a) => a.key));
}
// Check for protocol already existing
if (e instanceof Prisma.PrismaClientKnownRequestError) {
Expand Down

0 comments on commit 67234db

Please sign in to comment.