Search code examples
firebasegoogle-cloud-firestorefile-uploadfastapi

File Format is automatically changing to plain/text


I am using fastAPI to upload files through an endpoint and testing it using Postman. The endpoint is supposed to fetch the files and upload them to Firestore storage. However, the format of the files is getting stored as plain/text. Here's my endpoint

import os
import uuid
from typing import Optional

import requests
from dotenv import load_dotenv
from fastapi import APIRouter
from fastapi import status as response_status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from firebase_admin import credentials, firestore, initialize_app, storage
from starlette.datastructures import UploadFile
from starlette.requests import Request

@router.post("/saveEmailContent/")
async def save_email_content(request: Request):

    # Get the email data from the request
    form_data = await request.form()

    # Get the sender, recipient, subject and body from the email
    sender_email_id = form_data.get('sender')
    recipient_email_id = form_data.get('recipient')
    subject = form_data.get('subject')
    email_body = form_data.get('body-plain')
    attachment_count = form_data.get('attachment-count', 0)
    to_update_attachment_count = attachment_count == 0

    # Get the user and store ID from the recipient email ID 
    user_ref = firestore.client().collection('emailIds').where('emailId', '==', recipient_email_id).get()
    if len(user_ref) == 0:
        return JSONResponse(
            status_code=response_status.HTTP_404_NOT_FOUND,
            content={"message": "User not found"},
        )
    user_id = user_ref[0].to_dict().get('userId')
    store_id = user_ref[0].to_dict().get('storeId')

    # Add the email ID to the store document in the senderIds array 
    store_ref = firestore.client().collection('stores').document(store_id)
    store_ref.update({'senderIds': firestore.ArrayUnion([sender_email_id]), "updatedOn": firestore.SERVER_TIMESTAMP})

    # Upload attachments to Google Cloud Storage
    attachment_urls = []

    bucket_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_BUCKET_NAME")
    folder_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_FOLDER_NAME")

    for field_name, field_value in form_data.items():
        if isinstance(field_value, UploadFile):
            
            # Get the attachment from the request
            attachment = await field_value.read()

            # Update attachment count if update_flag is set to True 
            if to_update_attachment_count:
                attachment_count += 1

            # Generate a unique filename for each attachment
            unique_filename = f"{folder_name}/{uuid.uuid4().hex}"

            # Create a storage reference with the unique filename
            storage_ref = storage.bucket(bucket_name).blob(unique_filename)

            # Set the content type based on the original attachment's content type
            storage_ref.content_type = field_value.content_type
            print(f"Content-Type: {field_value.content_type}")

            # Upload the attachment to Firebase Cloud Storage
            storage_ref.upload_from_string(attachment)

            # Get the public URL of the uploaded attachment
            attachment_url = storage_ref.public_url
            print(attachment_url)

            # Add the attachment URL to the list
            attachment_urls.append(attachment_url)

    # Combine the data in a dictionary 
    email_data = {
        "senderEmailId": sender_email_id,
        "recipientEmailId": recipient_email_id,
        "subject": subject,
        "blurb": email_body[:25],
        "recepientUserId": user_id,
        "storeId": store_id,
        "attachment_count": attachment_count,
    }

    # Add the email data to the database 
    db = firestore.client()
    update_time, email_ref = db.collection('emails').add(email_data)

    # Add the email content to the email document
    email_ref.collection('content').add({
        'body': email_body,
        "attachments": attachment_urls,
    })
    email_data["attachments"] = attachment_urls

    return JSONResponse(
        status_code=response_status.HTTP_200_OK,
        content={"message": "Email content saved successfully", "emailData": email_data},
    )

My postman query looks like this

enter image description here

I am unable to figure out why the format is getting changed. When I explicitly try to change the format then I get the following error.

    "errors": [
      {
        "message": "Content-Type specified in the upload (text/plain) does not match Content-Type specified in metadata (image/jpeg). If it was a simple upload (uploadType=media), the Content-Type was specified as a bare header. If it was a multipart upload (uploadType=multipart), then the Content-Type was specified in the second part of the multipart. If it was a resumable upload (uploadType=resumable), then the Content-Type was specified with the X-Upload-Content-Type header with the start of the resumable session. For more information, see https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload.",
        "domain": "global",
        "reason": "invalid"
      }
    ]
  }
}

Any help with this?


Solution

  • I was able to solve it using the below method.

    # Upload attachments to Google Cloud Storage
        attachment_urls = []
    
        bucket_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_BUCKET_NAME")
        folder_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_FOLDER_NAME")
    
        # Iterate through the form data to get the attachments
        for field_name, field_value in form_data.items():
            if isinstance(field_value, UploadFile):
    
                # Get the attachment from the request as bytes
                attachment_bytes = await field_value.read()
    
                # Update attachment count if update_flag is set to True
                if to_update_attachment_count:
                    attachment_count += 1
    
                # Generate a unique filename for each attachment
                unique_filename = f"{folder_name}/{uuid.uuid4().hex}"
    
                # Create a storage reference with the unique filename
                bucket = storage.bucket(bucket_name)
                storage_ref = bucket.blob(unique_filename)
    
                # Set the content type based on the original attachment's content type
                content_type = field_value.content_type
                if not content_type:
                    # If content type is not provided, try to guess it based on the filename
                    content_type, _ = mimetypes.guess_type(field_value.filename)
    
                # Create a temporary file to store the attachment
                with tempfile.NamedTemporaryFile(delete=True) as temp_file:
    
                    # Write the attachment bytes to the temporary file
                    temp_file.write(attachment_bytes)
                    temp_file.seek(0)
    
                    # Upload the attachment file to Firebase Cloud Storage
                    storage_ref.upload_from_file(temp_file, content_type=content_type)
    
                    # Make the uploaded object publicly accessible
                    storage_ref.make_public()
    
                # Get the public URL of the uploaded attachment
                attachment_url = storage_ref.public_url
    
                # Add the attachment URL to the list
                attachment_urls.append(attachment_url)