Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added trace image upload to uploadResolver.ts #453

Merged
merged 2 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ POIGN_ART_SERVICE_ACTIVE=false
POIGN_ART_RECIPIENT_ADDRESS=0x66f59a4181f43b96fe929b711476be15c96b83b3
POIGN_ART_ORIGIN_ADDRESS=0x7a1dc1805f079a07ffd03845d3ec5b51ec8f9373
SYNC_POIGN_ART_CRONJOB_EXPRESSION=0 0 0 * * *
TRACE_FILE_UPLOADER_PASSWORD=hello_trace
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const envVars = [
'OUR_SECRET',
// 'XDAI_NODE_HTTP_URL',
'SEGMENT_API_KEY',
'TRACE_FILE_UPLOADER_PASSWORD',
];
// tslint:disable-next-line:class-name
interface requiredEnv {
Expand Down Expand Up @@ -76,6 +77,7 @@ interface requiredEnv {
OUR_SECRET: string;
XDAI_NODE_HTTP_URL: string;
SEGMENT_API_KEY: string;
TRACE_FILE_UPLOADER_PASSWORD: string;
}

class Config {
Expand Down
32 changes: 31 additions & 1 deletion src/middleware/pinataUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import config from '../config';

export const pinFile = (
file: ReadableStream,
filename: String,
filename: String = 'untitled',
encoding: string,
): Promise<AxiosResponse> => {
const data = new FormData();
Expand All @@ -34,3 +34,33 @@ export const pinFile = (
},
});
};

export const pinFileDataBase64 = (
fileDataBase64: string,
filename: string = 'untitled',
encoding: string,
): Promise<AxiosResponse> => {
const data = new FormData();
const array = fileDataBase64.split(',');

const base64FileData =
array.length > 1 && array[0].indexOf('base64') >= 0 ? array[1] : array[0];
const fileData = Buffer.from(base64FileData, 'base64');
data.append('file', fileData, { filename, encoding });

if (filename) {
const metadata = JSON.stringify({
name: filename,
});
data.append('pinataMetadata', metadata);
}

return Axios.post('https://api.pinata.cloud/pinning/pinFileToIPFS', data, {
maxContentLength: Infinity, // this is needed to prevent Axios from throw error with large files
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: config.get('PINATA_API_KEY') as string,
pinata_secret_api_key: config.get('PINATA_SECRET_API_KEY') as string,
},
});
};
1 change: 1 addition & 0 deletions src/modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ declare namespace NodeJS {
OUR_SECRET: string;
XDAI_NODE_HTTP_URL: string;
SEGMENT_API_KEY: string;
TRACE_FILE_UPLOADER_PASSWORD: string;
}
}
79 changes: 77 additions & 2 deletions src/resolvers/uploadResolver.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { Arg, Ctx, Field, InputType, Mutation, Resolver } from 'type-graphql';
import {
Arg,
Ctx,
Field,
InputType,
Mutation,
registerEnumType,
Resolver,
} from 'type-graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { MyContext } from '../types/MyContext';

import { pinFile } from '../middleware/pinataUtils';
import { pinFile, pinFileDataBase64 } from '../middleware/pinataUtils';
import { logger } from '../utils/logger';
import { getLoggedInUser } from '../services/authorizationServices';
import { errorMessages } from '../utils/errorMessages';
import SentryLogger from '../sentryLogger';
import { Readable } from 'stream';

@InputType()
export class FileUploadInputType {
Expand All @@ -14,6 +24,37 @@ export class FileUploadInputType {
image: FileUpload;
}

export enum TraceImageOwnerType {
USER = 'USER',
TRACE = 'TRACE',
CAMPAIGN = 'CAMPAIGN',
DAC = 'DAC',
}
registerEnumType(TraceImageOwnerType, {
name: 'TraceImageOwnerType',
description:
'The entity (e.g. user, trace, campaign, or community) type owns the image',
});

@InputType()
export class TraceFileUploadInputType {
// Client uploads image file
@Field()
fileDataBase64: string;

@Field()
user: string;

@Field()
entityId: string;

@Field(type => TraceImageOwnerType)
imageOwnerType: TraceImageOwnerType;

@Field()
password: string;
}

@Resolver()
export class UploadResolver {
@Mutation(() => String, { nullable: true })
Expand All @@ -35,4 +76,38 @@ export class UploadResolver {
throw Error(errorMessages.IPFS_IMAGE_UPLOAD_FAILED);
}
}

@Mutation(() => String, { nullable: true })
async traceImageUpload(
@Arg('traceFileUpload') traceFileUpload: TraceFileUploadInputType,
@Ctx() ctx: MyContext,
): Promise<String> {
const { fileDataBase64, user, imageOwnerType, password } = traceFileUpload;

let errorMessage;
if (!process.env.TRACE_FILE_UPLOADER_PASSWORD) {
errorMessage = `No password is defined for trace file uploader `;
} else if (password !== process.env.TRACE_FILE_UPLOADER_PASSWORD) {
errorMessage = `Invalid password to upload trace image from ip ${ctx?.req?.ip}`;
}

if (errorMessage) {
const userMessage = 'Access denied';
SentryLogger.captureMessage(errorMessage);
logger.error(errorMessage);
throw new Error(userMessage);
}

try {
const response = await pinFileDataBase64(
fileDataBase64,
undefined,
'base64',
);
return `/ipfs/${response.data.IpfsHash}`;
} catch (e) {
logger.error('upload() error', e);
throw Error(errorMessages.IPFS_IMAGE_UPLOAD_FAILED);
}
}
}