Skip to content

Commit cf68c40

Browse files
authored
Merge pull request #1183 from trycompai/claudio/fix-file-upload
[dev] [claudfuen] claudio/fix-file-upload
2 parents f16f9a0 + 50817b5 commit cf68c40

6 files changed

Lines changed: 218 additions & 341 deletions

File tree

Lines changed: 64 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use server';
22

33
import { BUCKET_NAME, s3Client } from '@/app/s3';
4-
import { auth } from '@/utils/auth';
4+
import { logger } from '@/utils/logger';
55
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
66
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
77
import { AttachmentEntityType, AttachmentType, db } from '@db';
88
import { revalidatePath } from 'next/cache';
9-
import { headers } from 'next/headers';
109
import { z } from 'zod';
10+
import { authActionClient } from '../safe-action';
1111

1212
function mapFileTypeToAttachmentType(fileType: string): AttachmentType {
1313
const type = fileType.split('/')[0];
@@ -34,94 +34,79 @@ const uploadAttachmentSchema = z.object({
3434
pathToRevalidate: z.string().optional(),
3535
});
3636

37-
export const uploadFile = async (input: z.infer<typeof uploadAttachmentSchema>) => {
38-
const { fileName, fileType, fileData, entityId, entityType, pathToRevalidate } = input;
39-
const session = await auth.api.getSession({ headers: await headers() });
40-
const organizationId = session?.session.activeOrganizationId;
37+
export const uploadFile = authActionClient
38+
.inputSchema(uploadAttachmentSchema)
39+
.metadata({
40+
name: 'uploadFile',
41+
track: {
42+
event: 'File Uploaded',
43+
channel: 'server',
44+
},
45+
})
46+
.action(async ({ parsedInput, ctx }) => {
47+
const { fileName, fileType, fileData, entityId, entityType, pathToRevalidate } = parsedInput;
48+
const { session } = ctx;
49+
const organizationId = session.activeOrganizationId;
50+
51+
logger.info(`[uploadFile] Starting upload for ${fileName} in org ${organizationId}`);
4152

42-
if (!organizationId) {
43-
return {
44-
success: false,
45-
error: 'Not authorized - no organization found',
46-
data: null,
47-
};
48-
}
49-
50-
try {
5153
const fileBuffer = Buffer.from(fileData, 'base64');
5254

53-
const MAX_FILE_SIZE_MB = 5;
55+
const MAX_FILE_SIZE_MB = 10;
5456
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
5557
if (fileBuffer.length > MAX_FILE_SIZE_BYTES) {
56-
return {
57-
success: false,
58-
error: `File exceeds the ${MAX_FILE_SIZE_MB}MB limit.`,
59-
data: null,
60-
};
58+
throw new Error(`File exceeds the ${MAX_FILE_SIZE_MB}MB limit.`);
6159
}
6260

6361
const timestamp = Date.now();
6462
const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
6563
const key = `${organizationId}/attachments/${entityType}/${entityId}/${timestamp}-${sanitizedFileName}`;
6664

67-
const putCommand = new PutObjectCommand({
68-
Bucket: BUCKET_NAME,
69-
Key: key,
70-
Body: fileBuffer,
71-
ContentType: fileType,
72-
});
73-
74-
await s3Client.send(putCommand);
75-
76-
console.log('Creating attachment...');
77-
console.log({
78-
name: fileName,
79-
url: key,
80-
type: mapFileTypeToAttachmentType(fileType),
81-
entityId: entityId,
82-
entityType: entityType,
83-
organizationId: organizationId,
84-
});
85-
86-
const attachment = await db.attachment.create({
87-
data: {
88-
name: fileName,
89-
url: key,
90-
type: mapFileTypeToAttachmentType(fileType),
91-
entityId: entityId,
92-
entityType: entityType,
93-
organizationId: organizationId,
94-
},
95-
});
96-
97-
const getCommand = new GetObjectCommand({
98-
Bucket: BUCKET_NAME,
99-
Key: key,
100-
});
65+
try {
66+
logger.info(`[uploadFile] Uploading to S3 with key: ${key}`);
67+
const putCommand = new PutObjectCommand({
68+
Bucket: BUCKET_NAME,
69+
Key: key,
70+
Body: fileBuffer,
71+
ContentType: fileType,
72+
});
73+
await s3Client.send(putCommand);
74+
logger.info(`[uploadFile] S3 upload successful for key: ${key}`);
75+
76+
logger.info(`[uploadFile] Creating attachment record in DB for key: ${key}`);
77+
const attachment = await db.attachment.create({
78+
data: {
79+
name: fileName,
80+
url: key,
81+
type: mapFileTypeToAttachmentType(fileType),
82+
entityId: entityId,
83+
entityType: entityType,
84+
organizationId: organizationId,
85+
},
86+
});
87+
logger.info(`[uploadFile] DB record created with id: ${attachment.id}`);
88+
89+
logger.info(`[uploadFile] Generating signed URL for key: ${key}`);
90+
const getCommand = new GetObjectCommand({
91+
Bucket: BUCKET_NAME,
92+
Key: key,
93+
});
94+
const signedUrl = await getSignedUrl(s3Client, getCommand, {
95+
expiresIn: 900,
96+
});
97+
logger.info(`[uploadFile] Signed URL generated for key: ${key}`);
98+
99+
if (pathToRevalidate) {
100+
revalidatePath(pathToRevalidate);
101+
}
101102

102-
const signedUrl = await getSignedUrl(s3Client, getCommand, {
103-
expiresIn: 900,
104-
});
105-
106-
if (pathToRevalidate) {
107-
revalidatePath(pathToRevalidate);
108-
}
109-
110-
return {
111-
success: true,
112-
data: {
103+
return {
113104
...attachment,
114105
signedUrl,
115-
},
116-
error: null,
117-
} as const;
118-
} catch (error) {
119-
console.error('Upload file action error:', error);
120-
121-
return {
122-
success: false,
123-
error: 'Failed to process file upload.',
124-
data: null,
125-
} as const;
126-
}
127-
};
106+
};
107+
} catch (error) {
108+
logger.error(`[uploadFile] Error during upload process for key ${key}:`, error);
109+
// Re-throw the error to be handled by the safe action client
110+
throw error;
111+
}
112+
});

apps/app/src/actions/safe-action.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ if (env.UPSTASH_REDIS_REST_URL && env.UPSTASH_REDIS_REST_TOKEN) {
2222
export const actionClientWithMeta = createSafeActionClient({
2323
handleServerError(e) {
2424
// Log the error for debugging
25-
logger('Server error:', e);
25+
logger.error('Server error:', e);
2626

2727
// Throw the error instead of returning it
2828
if (e instanceof Error) {
@@ -69,12 +69,12 @@ export const authActionClient = actionClientWithMeta
6969
});
7070

7171
if (process.env.NODE_ENV === 'development') {
72-
logger('Input ->', JSON.stringify(clientInput, null, 2));
73-
logger('Result ->', JSON.stringify(result.data, null, 2));
72+
logger.info('Input ->', JSON.stringify(clientInput, null, 2));
73+
logger.info('Result ->', JSON.stringify(result.data, null, 2));
7474

7575
// Also log validation errors if they exist
7676
if (result.validationErrors) {
77-
logger('Validation Errors ->', JSON.stringify(result.validationErrors, null, 2));
77+
logger.warn('Validation Errors ->', JSON.stringify(result.validationErrors, null, 2));
7878
}
7979

8080
return result;
@@ -212,7 +212,7 @@ export const authActionClient = actionClientWithMeta
212212
},
213213
});
214214
} catch (error) {
215-
logger('Audit log error:', error);
215+
logger.error('Audit log error:', error);
216216
}
217217

218218
// Add revalidation logic based on the cursor rules
@@ -274,12 +274,12 @@ export const authActionClientWithoutOrg = actionClientWithMeta
274274
});
275275

276276
if (process.env.NODE_ENV === 'development') {
277-
logger('Input ->', JSON.stringify(clientInput, null, 2));
278-
logger('Result ->', JSON.stringify(result.data, null, 2));
277+
logger.info('Input ->', JSON.stringify(clientInput, null, 2));
278+
logger.info('Result ->', JSON.stringify(result.data, null, 2));
279279

280280
// Also log validation errors if they exist
281281
if (result.validationErrors) {
282-
logger('Validation Errors ->', JSON.stringify(result.validationErrors, null, 2));
282+
logger.warn('Validation Errors ->', JSON.stringify(result.validationErrors, null, 2));
283283
}
284284

285285
return result;

0 commit comments

Comments
 (0)