Connect profile picture upload to backend (#533)

* Connect profile picture upload to backend

* Fix tests

* Revert onboarding state changes
This commit is contained in:
Charles Bochet
2023-07-07 17:50:02 -07:00
committed by GitHub
parent 6446692f25
commit a975935f49
21 changed files with 1522 additions and 1325 deletions

View File

@ -29,6 +29,7 @@ import GraphQLJSON from 'graphql-type-json';
error.extensions.stacktrace = undefined;
return error;
},
csrfPrevention: false,
}),
PrismaModule,
HealthModule,

View File

@ -1,6 +1,5 @@
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { v4 as uuidV4 } from 'uuid';
import { FileUploadService } from '../services/file-upload.service';
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
@ -21,18 +20,15 @@ export class FileUploadResolver {
): Promise<string> {
const stream = createReadStream();
const buffer = await streamToBuffer(stream);
const ext = filename.split('.')?.[1];
const id = uuidV4();
const name = `${id}${ext ? `.${ext}` : ''}`;
const path = await this.fileUploadService.uploadFile({
const { path } = await this.fileUploadService.uploadFile({
file: buffer,
name,
filename,
mimeType: mimetype,
fileFolder,
});
return path.name;
return path;
}
@Mutation(() => String)
@ -44,17 +40,14 @@ export class FileUploadResolver {
): Promise<string> {
const stream = createReadStream();
const buffer = await streamToBuffer(stream);
const ext = filename.split('.')?.[1];
const id = uuidV4();
const name = `${id}${ext ? `.${ext}` : ''}`;
const path = await this.fileUploadService.uploadImage({
const { paths } = await this.fileUploadService.uploadImage({
file: buffer,
name,
filename,
mimeType: mimetype,
fileFolder,
});
return path.name;
return paths[0];
}
}

View File

@ -4,45 +4,73 @@ import { getCropSize } from 'src/utils/image';
import { settings } from 'src/constants/settings';
import { FileFolder } from '../interfaces/file-folder.interface';
import { FileStorageService } from 'src/integrations/file-storage/file-storage.service';
import { v4 as uuidV4 } from 'uuid';
@Injectable()
export class FileUploadService {
constructor(private readonly fileStorage: FileStorageService) {}
async uploadFile({
private async _uploadFile({
file,
name,
filename,
mimeType,
fileFolder,
}: {
file: Buffer | Uint8Array | string;
name: string;
filename: string;
mimeType: string | undefined;
fileFolder: FileFolder;
}) {
await this.fileStorage.write({
file,
name,
name: filename,
mimeType,
folder: fileFolder,
});
}
async uploadFile({
file,
filename,
mimeType,
fileFolder,
}: {
file: Buffer | Uint8Array | string;
filename: string;
mimeType: string | undefined;
fileFolder: FileFolder;
}) {
const ext = filename.split('.')?.[1];
const id = uuidV4();
const name = `${id}${ext ? `.${ext}` : ''}`;
await this._uploadFile({
file,
filename,
mimeType,
fileFolder,
});
return {
name: `/${name}`,
path: `${fileFolder}/${name}`,
};
}
async uploadImage({
file,
name,
filename,
mimeType,
fileFolder,
}: {
file: Buffer | Uint8Array | string;
name: string;
filename: string;
mimeType: string | undefined;
fileFolder: FileFolder;
}) {
const ext = filename.split('.')?.[1];
const id = uuidV4();
const name = `${id}${ext ? `.${ext}` : ''}`;
// Get all cropSizes for this fileFolder
const cropSizes = settings.storage.imageCropSizes[fileFolder];
// Extract the values from ShortCropSize
@ -56,14 +84,18 @@ export class FileUploadService {
),
);
const paths: Array<string> = [];
// Upload all images to corresponding folders
await Promise.all(
images.map(async (image, index) => {
const buffer = await image.toBuffer();
paths.push(`profile-picture/${cropSizes[index]}/${name}`);
return this.uploadFile({
file: buffer,
name: `${cropSizes[index]}/${name}`,
filename: `${cropSizes[index]}/${name}`,
mimeType,
fileFolder,
});
@ -71,7 +103,7 @@ export class FileUploadService {
);
return {
name: `/${name}`,
paths,
};
}
}

View File

@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { FileModule } from '../file/file.module';
@Module({
imports: [],
imports: [FileModule],
providers: [UserService, UserResolver],
exports: [UserService],
})

View File

@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { UserResolver } from './user.resolver';
import { UserService } from './user.service';
import { AbilityFactory } from 'src/ability/ability.factory';
import { FileUploadService } from '../file/services/file-upload.service';
describe('UserResolver', () => {
let resolver: UserResolver;
@ -18,6 +19,10 @@ describe('UserResolver', () => {
provide: AbilityFactory,
useValue: {},
},
{
provide: FileUploadService,
useValue: {},
},
],
}).compile();

View File

@ -29,11 +29,18 @@ import { AuthUser } from 'src/decorators/auth-user.decorator';
import { assert } from 'src/utils/assert';
import { UpdateOneUserArgs } from '../@generated/user/update-one-user.args';
import { Prisma } from '@prisma/client';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
import { FileUploadService } from '../file/services/file-upload.service';
import { FileFolder } from '../file/interfaces/file-folder.interface';
@UseGuards(JwtAuthGuard)
@Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
constructor(
private readonly userService: UserService,
private readonly fileUploadService: FileUploadService,
) {}
@Query(() => User)
async currentUser(
@ -109,4 +116,31 @@ export class UserResolver {
displayName(@Parent() parent: User): string {
return `${parent.firstName ?? ''} ${parent.lastName ?? ''}`;
}
@Mutation(() => String)
async uploadProfilePicture(
@AuthUser() { id }: User,
@Args({ name: 'file', type: () => GraphQLUpload })
{ createReadStream, filename, mimetype }: FileUpload,
): Promise<string> {
const stream = createReadStream();
const buffer = await streamToBuffer(stream);
const fileFolder = FileFolder.ProfilePicture;
const { paths } = await this.fileUploadService.uploadImage({
file: buffer,
filename,
mimeType: mimetype,
fileFolder,
});
await this.userService.update({
where: { id },
data: {
avatarUrl: paths[0],
},
});
return paths[0];
}
}

View File

@ -4,7 +4,9 @@ import { AppModule } from './app.module';
import { graphqlUploadExpress } from 'graphql-upload';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const app = await NestFactory.create(AppModule, {
cors: true,
});
// Apply validation pipes globally
app.useGlobalPipes(new ValidationPipe());