Passing a pdf from Django backend to Angular frontend - django

I haven't been able to make any of the solutions to similar problems work for my case.
I would like to load a pdf from filesystem with django and return it via an API call to Angular so that it can be displayed. My Django code is pretty much:
class LoadPdfViewSet(views.APIView):
def get(self, request):
# some code here here
response = FileResponse(open(path_to_pdf, 'rb').read())
response.headers = {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment;filename="report.pdf"',
}
response.as_attachment = True
return response
while on the Angular side I have a service that does this:
export class LoadPdfService {
constructor(
private http: HttpClient
) {}
getPdf(): Observable<Blob> {
const params = new HttpParams({
fromObject: {
responsetype: 'arraybuffer'
// other stuff here
}
})
return self.http.get<Blob>(loadpdf_api_url, {params}).pipe(catchError(self.myErrorHandler))
}
}
and a component that tries to open the pdf like this:
export class MyComponent {
constructor(
public loadPdfService: LoadPdfService
) {}
download_pdf() {
let call = self.loadPdfService.getPdf();
call.subscribe( (response:Blob) => {
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
window.navigator.msSaveOrOpenBlob(blob, "report.pdf");
} else {
let pdfUrl = URL.createObjectURL(blob)
window.open(pdfUrl, '_blank')
URL.revokeObjectURL(pdfUrl);
}
}
}
}
but nothing happens. I have also tried using different responses and passthrough renderers on the django side, and Observable<Response> and .then() callbacks like
response.arrayBuffer().then(buffer => new Blob([buffer], {type: 'application/pdf'}))
on the Angular side. Sometimes I have managed to get the new window/tab to open but no pdf could be displayed.

I finally figured it out. In the python part, the read() can be removed with no problem. The issue was with the service response type and mapping of the response:
getPdf(): Observable<Blob> {
const options = {
params: new HttpParams({
fromObject: {
// my own parameters here
}
}),
responseType: 'blob' as 'json'
};
return this.http.get(this.url, options).pipe(
map(response => response as Blob),
catchError(this.myErrorHandler))
}

Related

Apollo Gateway: Forward Subgraph `set-cookie` Header to Gateway to Client

I have a subgraph microservice that handles sessions. We store our sessions via cookies that the subgraph creates, and should set it via the set-cookie header. Only issue is my gateway does not seem to be forwarding the set-cookie header from the subgraph to the client.
Here is the code for my gateway
const { ApolloServer } = require('apollo-server');
const { ApolloGateway, RemoteGraphQLDataSource } = require('#apollo/gateway');
const { readFileSync } = require('fs');
const supergraphSdl = readFileSync('./gateway/supergraph.graphql').toString();
class CookieDataSource extends RemoteGraphQLDataSource {
didReceiveResponse({ response, request, context }) {
const cookie = response.http.headers.get('set-cookie');
console.log("Cookie:", cookie)
return response;
}
}
const gateway = new ApolloGateway({
supergraphSdl,
buildService({url}) {
return new CookieDataSource({url});
}
});
const server = new ApolloServer({
gateway,
cors: {
origin: ["http://localhost:3000", "https://studio.apollographql.com"],
credentials: true
},
csrfPrevention: true,
});
server.listen().then(({ url }) => {
console.log(`🚀 Gateway ready at ${url}`);
}).catch(err => {console.error(err)});
version info
“#apollo/gateway”: “^2.1.2”,
“apollo-server”: “^3.10.2”,
I can confirm that the subgraph is sending back a set-cookie header, however, it is not being passed through to the client.
Thank you!
I ended up resolving the issue by creating both a gateway datasource that added context value. Then, pass the header from the subgraph context value to the response header.
import { GatewayGraphQLResponse, GatewayGraphQLRequestContext } from '#apollo/server-gateway-interface';
import { RemoteGraphQLDataSource } from '#apollo/gateway';
import { ApolloServerPlugin, GraphQLRequestContext, GraphQLRequestListener } from '#apollo/server';
interface ServerContext {
passthrough_cookies?: string
}
export class CookieProcessorDataSource extends RemoteGraphQLDataSource {
didReceiveResponse({response, context}: Required<Pick<GatewayGraphQLRequestContext<Record<string, any>>, 'request' | 'response' | 'context'>>): GatewayGraphQLResponse | Promise<GatewayGraphQLResponse> {
context.passthrough_cookies = response.http?.headers.get('set-cookie');
return response;
}
}
export class CookieServerListener implements GraphQLRequestListener<ServerContext> {
public willSendResponse({contextValue, response}: GraphQLRequestContext<ServerContext>): Promise<void> {
if (contextValue.passthrough_cookies !== undefined) {
response.http.headers.set('set-cookie', contextValue.passthrough_cookies);
}
return Promise.resolve()
}
}
export class CookieServerPlugin implements ApolloServerPlugin<ServerContext> {
async requestDidStart() {
return new CookieServerListener();
}
}

Trouble Writing to Jest Mocked Prisma Database

I have two databases that I need to interact with in my code. I have a simple function that takes an object and writes it to my PostgreSQL database using Prisma. I've tested the function with Postman, and it works perfectly, but when I try to execute it using a Jest mock (using the singleton pattern found in the Prisma unit testing guide), it returns undefined indicating that it didn't interact with the database and create the new record. Here's my code:
/prisma/clinical-schema.prisma
generator client {
provider = "prisma-client-js"
output = "./generated/clinical"
}
datasource clinicalDatabase {
provider = "postgresql"
url = "postgresql://postgres:postgres#localhost:5432/clinical-data?schema=public"
}
model pcc_webhook_update {
id Int #id #default(autoincrement())
event_type String
organization_id Int
facility_id Int
patient_id Int
resource_id String?
webhook_date DateTime #default(now()) #clinicalDatabase.Timestamptz(6)
status pcc_webhook_update_status #default(pending)
status_changed_date DateTime? #clinicalDatabase.Timestamptz(6)
error_count Int #default(0)
##unique([organization_id, facility_id, patient_id, resource_id, event_type, status])
}
enum pcc_webhook_update_status {
pending
processing
processed
error
}
/prisma/clinical-client.ts
import { PrismaClient } from './generated/clinical';
const prismaClinical = new PrismaClient();
export default prismaClinical;
/testing/prisma-clinical-mock.ts
import { PrismaClient } from '../prisma/generated/clinical';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import prisma from '../prisma/clinical-client';
jest.mock('../prisma/clinical-client', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}));
beforeEach(() => {
mockReset(prismaClinicalMock);
});
export const prismaClinicalMock = prisma as unknown as DeepMockProxy<PrismaClient>;
Everything up to this point follows the conventions outlined by the Prisma unit testing docs. The only modification I made was to make it database specific. Below is my function and tests. The request object in handle-pcc-webhooks.ts is a sample http request object, the body of which contains the webhook data I care about.
/functions/handle-pcc-webhooks/handler.ts
import prismaClinical from '../../../prisma/clinical-client';
import { pcc_webhook_update } from '../../../prisma/generated/clinical';
import { requestObject } from './handler.types';
export const handlePccWebhook = async (request: requestObject) => {
try {
const webhook = JSON.parse(request.body);
// if the webhook doesn't include a resource id array, set it to an array with an empty string to ensure processing and avoid violating
// the multi-column unique constraint on the table
const { resourceId: resourceIds = [''] } = webhook;
let records = [];
for (const resourceId of resourceIds) {
// update an existing record if one exists in the pending state, otherwise create a new entry
const record: pcc_webhook_update = await prismaClinical.pcc_webhook_update.upsert({
where: {
organization_id_facility_id_patient_id_resource_id_event_type_status: {
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
event_type: webhook.eventType,
status: 'pending'
}
},
update: {
webhook_date: new Date()
},
create: {
event_type: webhook.eventType,
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
status: 'pending' // not needed
}
});
records.push(record);
}
return records;
} catch (error) {
console.error(error);
}
};
/functions/handle-pcc-webhooks/handler.spec.ts
import fs from 'fs';
import path from 'path';
import MockDate from 'mockdate';
import { prismaClinicalMock } from '../../../testing/prisma-clinical-mock';
import { createAllergyAddRecord } from './__mocks__/allergy';
import { requestObject } from './handler.types';
import { handlePccWebhook } from './handler';
describe('allergy.add', () => {
let requestObject: requestObject;
let allergyAddRecord: any;
beforeAll(() => {
requestObject = getRequestObject('allergy.add');
});
beforeEach(() => {
MockDate.set(new Date('1/1/2022'));
allergyAddRecord = createAllergyAddRecord(new Date());
});
afterEach(() => {
MockDate.reset();
});
test('should create an allergy.add database entry', async() => {
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
// this is where I would expect handlePccWebhook to return the newly created database
// record, but instead it returns undefined. If I run the function outside of this
// unit test, with the same input value, it functions perfectly
await expect(handlePccWebhook(requestObject)).resolves.toEqual([allergyAddRecord]);
});
});
// This just builds a request object with the current webhook being tested
function getRequestObject(webhookType: string) {
// read the contents of request object file as a buffer, then convert it to JSON
const rawRequestObject = fs.readFileSync(path.resolve(__dirname, '../../sample-data/handle-pcc-webhook-request.json'));
const requestObject: requestObject = JSON.parse(rawRequestObject.toString());
// read the contents of the webhook file as a buffer, then convert it to a string
const rawWebhook = fs.readFileSync(path.resolve(__dirname, `../../sample-data/${webhookType}.json`));
const webhookString = rawWebhook.toString();
// set the body of the request object to the contents of the target webhook
requestObject.body = webhookString;
return requestObject;
}
Finally, here is the result of running the unit test:
So after banging my had against the wall for a few hours, I figured out the issue. In my handler.spec.ts file, I had the following line:
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
what that does is mock the value returned for any create functions run using Prisma. The issue is that my function is using an upsert function, which I wasn't explicitly mocking, thus returning undefined. I changed the above line to
prismaClinicalMock.pcc_webhook_update.upsert.mockResolvedValue(allergyAddRecord);
and it started working.

How to access request headers in loopback4?

I used a simple statement like below to access headers in loopback4.
console.log(request.headers);
But it is printing undefined. A sample request headers that I want to access is in the image.
request header image
I am receiving the request and its headers which is perfectly fine. It's just that I am not able to access its headers as I am getting undefined from request.headers.
I am a beginner in loopback so pls explain it.
If i have to use a bodyparser then how i would have to use it in loopback4 because it is different from express.
Update
The original answer, although valid, is not the recommended way. Use dependency injection instead:
import {inject} from '#loopback/core';
import {get, param} from '#loopback/rest';
export class SomethingController {
constructor() {}
#get('/something')
something(#param.header.string('x-your-header') yourHeader: string): void {
// Use your header.
// e.g. Log to console
console.log(yourHeader);
}
}
Unlike the REQUEST object, this strips away unnecessary info and provides built-in coercion.
Further reading
https://loopback.io/doc/en/lb4/Decorators_openapi.html#parameter-decorator
https://loopback.io/doc/en/lb4/apidocs.openapi-v3.param.html
Original answer
If you're attempting to access the headers in a Controller, then you can inject the REQUEST object:
import {inject} from '#loopback/core';
import {get, Request, RestBindings} from '#loopback/rest';
export class SomethingController {
constructor(#inject(RestBindings.Http.REQUEST) private req: Request) {}
#get('/something')
something(): void {
// Get the headers
this.req.headers;
}
}
You can also use Context, example
import {inject} from '#loopback/core';
import {
post, RequestContext
} from '#loopback/rest';
export class UserController {
constructor(
#inject.context()
public context: RequestContext,
) {}
#post('/users/logout', {
responses: {
'200': {
description: 'Return success',
content: {
'application/json': {
schema: {
type: 'object'
},
},
},
},
},
})
async logout(): Promise<object> {
// ensure the token exists
const authHeader = this.context.request.headers.authorization;
if(authHeader && authHeader.split(" ")[1]){
// remove token
return {code:204, status: true, message:'Logout successful'};
}else{
return {code: 404, status: false, message:'Something went wrong'};
}
}
}
In sequence.ts, request object is called via context.
const {request, response} = context;
console.log(request.headers)
add log in sequence.ts to get the request headers.

Angular HttpClient get request - how to get the text data sent?

I have an Angular/Ionic app that communicates with a Django backend. I am using this.http.get() to communicate with this server (on Heroku) and the Django server should be sending the text "OK". Instead, I am either (dependent on specific usage of this.http.get()) getting an error where the statusText is the text I want, or something like Object { _isScalar: false, source: {…}, operator: {…} }
My Django code is simple:
def make(request, otherParams):
...
return HttpResponse("OK")
I know that the get() has made it to the server, because the server runs certain things when the corresponding function is called.
How do I, from the Angular frontend, detect if the Django script has sent the "OK" or not?
(The error is not due to any of various CORS policies, I have installed django-cors-headers)
EDIT:
if it's relevant, I'm on a Windows PC, testing on localhost/Firefox Nightly with Ionic 5 and Angular 9.
Here is my frontend code, cutting the irrelevant bits. The way I've made my GET request is not consistent, having tried many. This one is suggested in the below post, and still fails.
import { Component, OnInit } from '#angular/core';
import { AlertController } from '#ionic/angular';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-submit',
templateUrl: './submit.page.html',
styleUrls: ['./submit.page.scss'],
})
export class SubmitPage implements OnInit {
constructor(public alertController: AlertController, private http: HttpClient) { }
ngOnInit() {
}
//irrelevant variable-getting
save() {
console.log(this.list);
if (this.title == null || this.title == "") {
this.presentAlert("Uncompleted fields", "Please complete the Title field!");
}
else if (this.sub == null || this.sub == "") {
this.presentAlert("Uncompleted fields", "Please complete the Subtitle field!");
}
else if (this.content == null || this.content == "") {
this.presentAlert("Uncompleted fields", "Please complete the Content field!");
} else {
try {
if (this.list.length == 0) {
console.log(this.list);
throw "empty list";
}
//more irrelevance
}
catch{ this.presentAlert("Uncompleted fields", "Please complete the list!"); }
if (temp2) {
this.makePost();
}
}
}
makePost() {
var temp = (<root url> + encodeURIComponent(this.title) + `/` + (this.posterID).toString() + '/' + encodeURIComponent(this.sub) + '/' + encodeURIComponent(this.content) + '/' + this.happy.toString() + '/' + this.angry.toString() + `/` + this.stressy.toString() + `/` + this.energy.toString() + '/' + this.worry.toString());
console.log(temp);
this.http.get(temp).toPromise()
.then(r => console.log('response', r)).catch(error => console.error(error));
}
}
Assuming you are using the HttpClient to invoke your GET request, you need to actually do something with this.http.get().
Try doing something like this instead:
If you can use async/await
const response = await this.http.get(<url>);
If you cannot use async/await
this.http.get(<url>).then(r => console.log('response', r) ).catch( error => console.error(error) );
If you just do:
const response = this.http.get(<url>);
console.log(response);
You are effectively logging the Promise and not the resolved Promise that holds the data you're after.
If you can show more code from your Angular app, it would help determine if this is your problem or not. For basic troubleshooting, I would first validate that your GET request (in your Python app) works by itself. Using Postman, you can test this (along with methods). If you GET request works fine, then the issue is more than likely something in you angular app which I described how to fix above.
It turned out that my Angular script was trying to interpret the response as JSON, not the plaintext I wanted. Using the code from the answer by mwilson and adding { responseType: 'text' } into the get() parameters, the console now logs the response successfully.
My The code snippet now looks like this: this.http.get(url, { responseType:'text'}).toPromise().then(r => console.log(r)).catch(error => console.error(error));
BTW feel free to point out any improvements/optimizations to the above code if you feel it needs it.

AWS S3 Bucket Upload using CollectionFS and cfs-s3 meteor package

I am using Meteor.js with Amazon S3 Bucket for uploading and storing photos. I am using the meteorite packges collectionFS and aws-s3. I have setup my aws-s3 connection correctly and the images collection is working fine.
Client side event handler:
'click .submit': function(evt, templ) {
var user = Meteor.user();
var photoFile = $('#photoInput').get(0).files[0];
if(photoFile){
var readPhoto = new FileReader();
readPhoto.onload = function(event) {
photodata = event.target.result;
console.log("calling method");
Meteor.call('uploadPhoto', photodata, user);
};
}
And my server side method:
'uploadPhoto': function uploadPhoto(photodata, user) {
var tag = Random.id([10] + "jpg");
var photoObj = new FS.File({name: tag});
photoObj.attachData(photodata);
console.log("s3 method called");
Images.insert(photoObj, function (err, fileObj) {
if(err){
console.log(err, err.stack)
}else{
console.log(fileObj._id);
}
});
The file that is selected is a .jpg image file but upon upload I get this error on the server method:
Exception while invoking method 'uploadPhoto' Error: DataMan constructor received data that it doesn't support
And no matter whether I directly pass the image file, or attach it as data or use the fileReader to read as text/binary/string. I still get that error. Please advise.
Ok, maybe some thoughts. I have done things with collectionFS some months ago, so take care to the docs, because my examples maybe not 100% correct.
Credentials should be set via environment variables. So your key and secret is available on server only. Check this link for further reading.
Ok first, here is some example code which is working for me. Check yours for differences.
Template helper:
'dropped #dropzone': function(event, template) {
addImage(event);
}
Function addImage:
function addImagePreview(event) {
//Go throw each file,
FS.Utility.eachFile(event, function(file) {
//Some Validationchecks
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
var fsFile = new FS.File(image.src);
//setMetadata, that is validated in collection
//just own user can update/remove fsFile
fsFile.metadata = {owner: Meteor.userId()};
PostImages.insert(fsFile, function (err, fileObj) {
if(err) {
console.log(err);
}
});
};
})(file);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
});
}
Ok, your next point is the validation. The validation can be done with allow/deny rules and with a filter on the FS.Collection. This way you can do all your validation AND insert via client.
Example:
PostImages = new FS.Collection('profileImages', {
stores: [profileImagesStore],
filter: {
maxSize: 3145728,
allow: {
contentTypes: ['image/*'],
extensions: ['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG']
}
},
onInvalid: function(message) {
console.log(message);
}
});
PostImages.allow({
insert: function(userId, doc) {
return (userId && doc.metadata.owner === userId);
},
update: function(userId, doc, fieldNames, modifier) {
return (userId === doc.metadata.owner);
},
remove: function(userId, doc) {
return false;
},
download: function(userId) {
return true;
},
fetch: []
});
Here you will find another example click
Another point of error is maybe your aws configuration. Have you done everything like it is written here?
Based on this post click it seems that this error occures when FS.File() is not constructed correctly. So maybe this should be you first way to start.
A lot for reading so i hope this helps you :)