Cannot upload images to S3 from Django backend and Nextjs frontend vercel - django

I have an e-commerce site built with Django and nextjs. Previously it was hosted on hostinger but recently I moved the API backend to AWS Lambda and the frontend to vercel. I'm storing the product images in S3. The problem I;m facing is after the migration, I'm not able to upload the images from my nextjs frontend. I have tried uploading the images from postman and Django admin panel and it worked. It just not works from the nextjs frontend.
Here is the error:
An error occurred (400) when calling the HeadObject operation: Bad Request
I'm using django-storages to upload my images to S3.
I'm using FormData to send images from frontend to backend.
frontend submit function:
function submit() {
setIsLoading(true);
var myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${session?.token?.access}`);
myHeaders.append("Cookie", "django_language=en");
var formdata = new FormData();
formdata.append("name", name);
formdata.append("desc", desc);
formdata.append("length", length);
formdata.append("height", height);
formdata.append("width", width);
formdata.append("measure", measurements);
formdata.append("origin", origin);
formdata.append("signed", signed);
formdata.append("reproduce", reproduce);
formdata.append("reproduce_days", reproduce_days);
formdata.append("image", img);
formdata.append("reg_price", price);
formdata.append("cat", category);
formdata.append("year", year);
formdata.append("newcat", otherCategory);
formdata.append("newsubcat", otherSubCategory);
formdata.append("newmedium", otherMedium);
formdata.append("subcat", subCategory);
formdata.append("medium", medium);
formdata.append("orientation", orientation);
formdata.append("stock_quantity", quantity);
var requestOptions = {
method: "POST",
headers: myHeaders,
body: formdata,
redirect: "follow",
};
fetch(
`${API_URL}/profile/vendor/${session?.token?.user?.id}/add-product/?curr=INR`,
requestOptions
)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
}
Note: My Lambda function and S3 bucket are not in the same region.

Related

AWS Amplify post request fails with "status code 403 at node_modules/axios"

I configured and initialized AWS Amplify for my ReactNative/Expo app and added a REST Api. Im new to AWS in general, but im assuming that once I add the API, my project is populated with amplify/backend folders and files and is ready for consumption.
So i tried to create a simple post request to create an item in my DynamoDB table with
import { Amplify, API } from "aws-amplify";
import awsconfig from "./src/aws-exports";
Amplify.configure(awsconfig);
const enterData = async () => {
API.post("API", "/", {
body: {
dateID: "testing",
},
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`
}
})
.then((result) => {
// console.log(JSON.parse(result));
})
.catch((err) => {
console.log(err);
});
};
const signIn = async () => {
Auth.signIn('test#test.com', 'testpassword')
.then((data) => {
console.log(data)
enterData() //enterData is attempted after signin is confirmed.
})
.catch((err) => {
console.log(err)
})
}
signIn()
I did not touch anything else in my project folder besides including the above in my App.tsx because im unsure if i need to and where. I got a 403 error code and it "points" to the axios package but im not sure if issue is related to aws integration.
I configured the REST Api with restricted access where Authenticated users are allowed to CRUD, and guests are allowed to Read. How could I even check if I am considered an "Authorized User" .
Yes, AWS Amplify API category uses Axios under the hood so axios is related to your problem.
Probably you get 403 because you didn't authorized, for Rest API's you need to set authorization headers,
I don't know how is your config but you can take help from this page. Please review the "Define Authorization Rules" section under the API(REST) section.
https://docs.amplify.aws/lib/restapi/authz/q/platform/js/#customizing-http-request-headers
To check authorization methods, you can use "Auth" class like that also you can see auth class usage in the above link.
import { Amplify, API, Auth } from "aws-amplify";
https://aws-amplify.github.io/amplify-js/api/classes/authclass.html

How to upload image by using AWS Gateway Generated SDK

I am working on uploading an image to S3 by only using AWS gateway. The workflow is working now and I can upload file to S3, but the file encode and decode failed. The postman could work well to upload image to S3, but I don't know how it did.
I am using API Gateway generate JS SDK
Here is the uploading function
function uploadFile(file) {
const reader = new FileReader();
reader.onload = () => {
var params = {
'key': file.name,
};
var body = reader.result.split(',')[1]
var additionalParams = {
headers: {
'Content-Type': file.type,
}
};
sdk.uploadPut(params, body, additionalParams)
.then(function (result) {
console.log('this is upload result', result)
}).catch(function (result) {
console.log('this is upload error', result)
});
}
reader.readAsDataURL(file);
}
and This is API Gateway integration request
It seems I am uploading a base64 string to .jpg rather than raw data.
Can I get some tip on this?

iOS can't play uploaded audio: JS MediaRecorder -> Blob -> Django Server -> AWS s3 -> JS decodeAudioData --> "EncodingError: Decoding Failed"

Answer: shouldn't set content/mime type browser side with JS, should use native browser mimeType then convert server side (I used PyDub).
Question:
I am using Javascript MediaRecorder, Django, AWS s3 and Javascript Web Audio API to record audio files for users to share voice notes with one another. I've seen disbursed answers online about how to record and upload audio data and the issues with Safari/iOS but thought this could be a thread to bring it together and confront some of these issues.
Javascript:
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onstop = function (e) {
var blob = new Blob(
chunks,
{
type:"audio/mp3",
}
);
var formdata = new FormData();
formdata.append('recording', blob)
var resp = await fetch(url, { // Your POST endpoint
method: 'POST',
mode: 'same-origin',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrf_token,
},
body: formdata,
})
}
Django:
for k,file in request.FILES.items():
sub_path = "recordings/audio.mp3"
meta_data = {"ContentType":"audio/mp3"}
s3.upload_fileobj(file, S3_BUCKET_NAME, sub_path,ExtraArgs=meta_data)
###then some code to save the s3 URL to my database for future retrieval
Javascript:
var audio_context = new AudioContext();
document.addEventListener("#play-audio","click", function(e) {
var url = "https://docplat-bucket.s3.eu-west-3.amazonaws.com/recordings/audio.mp3"
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function () {
audio_context.decodeAudioData(request.response, function (buffer) {
playSound(buffer)
});
}
request.send();
})
Results in:
"EncodingError: Decoding Failed"
Note however that using the w3 schools demo mp3 url does play the recording:
https://docplat-bucket.s3.eu-west-3.amazonaws.com/recordings/t-rex-roar.mp3
Specs:
PC (used to upload recoding): Windows 11, Chrome Version 98.0.4758.81 (Official Build) (64-bit)
Django: Version: 3.1.7
Mobile (used to play recording): iPhone X, iOS (Version 14.7.1)
Problematic url: https://docplat-bucket.s3.eu-west-3.amazonaws.com/recordings/audio.mp3
Working url: https://docplat-bucket.s3.eu-west-3.amazonaws.com/recordings/t-rex-roar.mp3
(This is my first post so please forgive me if I haven't asked this question in the ideal way :) )
When you upload the recorded Blob you set the type to 'audio/mp3'. But unless you use a custom library which patches the MediaRecorder the mimeType of the recording will be whatever the browser likes best.
As of now it's 'audio/opus' in Firefox and 'audio/webm' in Chrome.
If you define your Blob like this it should work.
var blob = new Blob(
chunks,
{
type: mediaRecorder.mimeType
}
);
You would also have to change your server side code to not use 'audio/mp3' anymore.

Upload file to s3 from vuejs and Django backend

I would like to generate a signed URL in Django, send it to frontend using Axios Ajax and then using that url upload a file directly from Vue JS to S3. In following code when user clicks on upload button - Vue method uploadButtonClicked is called which calls Django function ajaxSendPresignedUrlForS3 which generates presigned post url. This url is passed back to vue uploadButtonClicked and then vue method uploadFile is called.
So far url generation is sucessful. But on posting file to S3 bucket I get error Error: Request failed with status code 403. I have been reading around and making some modifications with the code which results in new errors like 412, 405 etc.
Django code
def ajaxSendPresignedUrlForS3(request):
input=json.loads(request.body.decode('utf-8')); print('ajaxSendPresignedUrlForS3');
S3_BUCKET = os.environ.get('S3_BUCKET')
file_name = input['file_name'][0]
file_type = input['file_type'][0]
s3 = boto3.client('s3',
aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY'),
region_name='us-east-2',
config=Config(signature_version='s3v4'),
)
presigned_post = s3.generate_presigned_post(
Bucket = S3_BUCKET,
Key = file_name,
Fields = {"Content-Type": 'multipart/form-data'},
Conditions = [{"Content-Type": 'multipart/form-data'}],
ExpiresIn = 300 #seconds
)
return JsonResponse({'data': presigned_post})
Javascript Vue code
Vue method 1 :
uploadButtonClicked:function(){ //gettting presigned POST URL from django
console.log('uploadButtonClicked');
//call axios ajax to get presigned URL from django and then upload file to s3 using axios func "upLoadFile"
axios({
method: 'post',
baseURL: window.location.origin, //we need base url coz this ajax can be called from any page on timeout
url: 'main/ajaxSendPresignedUrlForS3/',
data: {
file_name: this.inputFilesName,
file_type: this.inputFilesType,
},
responseType: 'json', //server will response with this datatype
})
.then ( function (response){
data=response.data;
console.log('uploadButtonClicked succes. data =',data ); //works
this.upLoadFile(data); //upload file to S3
}.bind(this))
.catch ( function (error){
console.log('uploadButtonClicked error=',error);
});
},
Vue method 2:
upLoadFile:function(data){ //upload file directly to s3
console.log('upLoadFile')
var postData = new FormData(); //its type to JS inbuilt form
console.log('data.data.fields=',data.data.fields,'\nKeys =')
for(key in data.data.fields){
console.log(key);
postData.append(key, data.data.fields[key]);
}
postData.append('file', document.getElementById('id_inputButtonReal').files[0]);
console.log('postData=',postData)
axios({
method: 'get',
url: data.data.url+new Date().getTime(),
data: {
postData: postData,
},
// responseType: 'json', //server will response with this datatype
})
.then ( function (response){
data=response.data;
console.log('upLoadFile success');
}.bind(this))
.catch ( function (error){
console.log('upLoadFile error=',error);
});
},
I was able to upload file to s3 directly from Django though. Which probably means my python part is correct:
from boto3.s3.transfer import S3Transfer
myfile='/home/user/img1.jpg';
transfer = S3Transfer(s3); #s3 is declared in above code
transfer.upload_file(myfile, S3_BUCKET,'snake2.jpg') ; print('upload successful');
Thanks
Error explaination: I was sending data to S3 in form of dictionary of dictionary. S3 likes direct postData.
Above Django code is perfectly fine. I have consolidated Javascript code into a smaller version below for comprehension.
uploadButtonClickedforStackoverflow:function(){ //gettting presigned POST URL from django and then uploading file to S3 using that url
axios({
method: 'post',
baseURL: window.location.origin, //we need base url coz this ajax can be called from any page on timeout
url: 'main/ajaxSendPresignedUrlForS3/',
data: { file_name: 'snake.jpg' },//sending filename to django so it can build a presigned POST URL granting frontend permission to upload
responseType: 'json', //server will response with this datatype
})
.then ( function (response){
presigned_post=response.data
var postData = new FormData(); //its type to JS inbuilt form
for(key in presigned_post.fields){ //appending all keys sent by django to our JS FormData object
postData.append(key, presigned_post.fields[key]);
}
var file=document.getElementById('id_inputButtonReal').files[0]; //id_inputButtonReal is id of HTML input element
postData.append('file', file);
axios({
method: 'post',
url: presigned_post.url,
data: postData, //dont sent data as {data:postData}, instead S3 likes it just by itself
})
});
},

2 docker containers (Django & React), isomorphic-fetch and a login request

My setup is running on Docker with a frontend (React) as well as a backend (Django) container.
I'm using the login-form component of the drf-react-app below in another project and am clueless as to how the api fetch request in the loginUser action creator (src/actions/user.js) knows which URL it is supposed to use..?
user.js:22 POST http://localhost:3000/api/obtain-auth-token/ 404 (Not Found)
I want it to send the request to the server at port 8000. I took the code from this drf-react boilerplate: https://github.com/moritz91/drf-react-login
export function loginUser(username, password) {
return (dispatch, getState) => {
const payload = {username, password};
dispatch({type: LOGIN_USER_REQUEST, payload});
return fetch(`/api/obtain-auth-token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
})
.then(handleResponse(dispatch, LOGIN_USER_RESPONSE))
.then((json) => {
saveUser(json);
return json;
})
.catch(handleError(dispatch, LOGIN_USER_RESPONSE))
}
}
What am I missing?
In your package.json you have a proxy property set to "http://backend:8000". The proxy is used to redirect requests to a given url when you make a request against your local server http://localhost:3000. So if that's not working then you might be missing a step that enables the proxy.