11'use server' ;
22
33import { BUCKET_NAME , s3Client } from '@/app/s3' ;
4- import { auth } from '@/utils/auth ' ;
4+ import { logger } from '@/utils/logger ' ;
55import { GetObjectCommand , PutObjectCommand } from '@aws-sdk/client-s3' ;
66import { getSignedUrl } from '@aws-sdk/s3-request-presigner' ;
77import { AttachmentEntityType , AttachmentType , db } from '@db' ;
88import { revalidatePath } from 'next/cache' ;
9- import { headers } from 'next/headers' ;
109import { z } from 'zod' ;
10+ import { authActionClient } from '../safe-action' ;
1111
1212function 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 - z A - Z 0 - 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+ } ) ;
0 commit comments