Skip to content
Merged
12 changes: 7 additions & 5 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
"name": "@comp/app",
"version": "0.1.0",
"dependencies": {
"@ai-sdk/groq": "^1.2.8",
"@ai-sdk/openai": "^1.3.19",
"@ai-sdk/provider": "^1.1.3",
"@ai-sdk/react": "^1.2.9",
"@ai-sdk/anthropic": "^2.0.0",
"@ai-sdk/groq": "^2.0.0",
"@ai-sdk/openai": "^2.0.0",
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/react": "^2.0.0",
"@ai-sdk/rsc": "^1.0.0",
"@aws-sdk/client-s3": "^3.806.0",
"@aws-sdk/client-sts": "^3.808.0",
"@aws-sdk/s3-request-presigner": "^3.832.0",
Expand Down Expand Up @@ -50,7 +52,7 @@
"@uploadthing/react": "^7.3.0",
"@upstash/ratelimit": "^2.0.5",
"@vercel/sdk": "^1.7.1",
"ai": "^4.3.16",
"ai": "^5.0.0",
"axios": "^1.9.0",
"better-auth": "^1.2.8",
"canvas-confetti": "^1.9.3",
Expand Down
124 changes: 64 additions & 60 deletions apps/app/src/actions/files/upload-file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use server';

import { BUCKET_NAME, s3Client } from '@/app/s3';
import { auth } from '@/utils/auth';
import { logger } from '@/utils/logger';

// This log will run as soon as the module is loaded.
Expand All @@ -10,8 +11,8 @@ import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { AttachmentEntityType, AttachmentType, db } from '@db';
import { revalidatePath } from 'next/cache';
import { headers } from 'next/headers';
import { z } from 'zod';
import { authActionClient } from '../safe-action';

function mapFileTypeToAttachmentType(fileType: string): AttachmentType {
const type = fileType.split('/')[0];
Expand All @@ -38,19 +39,18 @@ const uploadAttachmentSchema = z.object({
pathToRevalidate: z.string().optional(),
});

export const uploadFile = authActionClient
.inputSchema(uploadAttachmentSchema)
.metadata({
name: 'uploadFile',
track: {
event: 'File Uploaded',
channel: 'server',
},
})
.action(async ({ parsedInput, ctx }) => {
const { fileName, fileType, fileData, entityId, entityType, pathToRevalidate } = parsedInput;
const { session } = ctx;
const organizationId = session.activeOrganizationId;
export const uploadFile = async (input: z.infer<typeof uploadAttachmentSchema>) => {
logger.info(`[uploadFile] Starting upload for ${input.fileName}`);
try {
const { fileName, fileType, fileData, entityId, entityType, pathToRevalidate } =
uploadAttachmentSchema.parse(input);

const session = await auth.api.getSession({ headers: await headers() });
const organizationId = session?.session.activeOrganizationId;

if (!organizationId) {
throw new Error('Not authorized - no organization found');
}

logger.info(`[uploadFile] Starting upload for ${fileName} in org ${organizationId}`);

Expand All @@ -66,51 +66,55 @@ export const uploadFile = authActionClient
const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
const key = `${organizationId}/attachments/${entityType}/${entityId}/${timestamp}-${sanitizedFileName}`;

try {
logger.info(`[uploadFile] Uploading to S3 with key: ${key}`);
const putCommand = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
Body: fileBuffer,
ContentType: fileType,
});
await s3Client.send(putCommand);
logger.info(`[uploadFile] S3 upload successful for key: ${key}`);

logger.info(`[uploadFile] Creating attachment record in DB for key: ${key}`);
const attachment = await db.attachment.create({
data: {
name: fileName,
url: key,
type: mapFileTypeToAttachmentType(fileType),
entityId: entityId,
entityType: entityType,
organizationId: organizationId,
},
});
logger.info(`[uploadFile] DB record created with id: ${attachment.id}`);

logger.info(`[uploadFile] Generating signed URL for key: ${key}`);
const getCommand = new GetObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
});
const signedUrl = await getSignedUrl(s3Client, getCommand, {
expiresIn: 900,
});
logger.info(`[uploadFile] Signed URL generated for key: ${key}`);

if (pathToRevalidate) {
revalidatePath(pathToRevalidate);
}

return {
logger.info(`[uploadFile] Uploading to S3 with key: ${key}`);
const putCommand = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
Body: fileBuffer,
ContentType: fileType,
});
await s3Client.send(putCommand);
logger.info(`[uploadFile] S3 upload successful for key: ${key}`);

logger.info(`[uploadFile] Creating attachment record in DB for key: ${key}`);
const attachment = await db.attachment.create({
data: {
name: fileName,
url: key,
type: mapFileTypeToAttachmentType(fileType),
entityId: entityId,
entityType: entityType,
organizationId: organizationId,
},
});
logger.info(`[uploadFile] DB record created with id: ${attachment.id}`);

logger.info(`[uploadFile] Generating signed URL for key: ${key}`);
const getCommand = new GetObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
});
const signedUrl = await getSignedUrl(s3Client, getCommand, {
expiresIn: 900,
});
logger.info(`[uploadFile] Signed URL generated for key: ${key}`);

if (pathToRevalidate) {
revalidatePath(pathToRevalidate);
}

return {
success: true,
data: {
...attachment,
signedUrl,
};
} catch (error) {
logger.error(`[uploadFile] Error during upload process for key ${key}:`, error);
// Re-throw the error to be handled by the safe action client
throw error;
}
});
},
} as const;
} catch (error) {
logger.error(`[uploadFile] Error during upload process:`, error);
return {
success: false,
error: error instanceof Error ? error.message : 'An unknown error occurred.',
} as const;
}
};
Loading
Loading