feat: upload profile picture from google (#964)
* feat: upload profile picture from google * fix: only add profile picture if user don't have any
This commit is contained in:
@ -5,6 +5,7 @@ import { PrismaService } from 'src/database/prisma.service';
|
||||
import { UserModule } from 'src/core/user/user.module';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -27,7 +28,7 @@ const jwtModule = JwtModule.registerAsync({
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [jwtModule, UserModule, WorkspaceModule],
|
||||
imports: [jwtModule, UserModule, WorkspaceModule, FileModule],
|
||||
controllers: [GoogleAuthController, VerifyAuthController],
|
||||
providers: [
|
||||
AuthService,
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Response } from 'express';
|
||||
import FileType from 'file-type';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { GoogleRequest } from 'src/core/auth/strategies/google.auth.strategy';
|
||||
import { UserService } from 'src/core/user/user.service';
|
||||
@ -9,6 +13,8 @@ import { GoogleProviderEnabledGuard } from 'src/core/auth/guards/google-provider
|
||||
import { GoogleOauthGuard } from 'src/core/auth/guards/google-oauth.guard';
|
||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
|
||||
@Controller('auth/google')
|
||||
export class GoogleAuthController {
|
||||
@ -17,6 +23,7 @@ export class GoogleAuthController {
|
||||
private readonly userService: UserService,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ -29,7 +36,8 @@ export class GoogleAuthController {
|
||||
@Get('redirect')
|
||||
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard)
|
||||
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
|
||||
const { firstName, lastName, email, workspaceInviteHash } = req.user;
|
||||
const { firstName, lastName, email, picture, workspaceInviteHash } =
|
||||
req.user;
|
||||
|
||||
let workspaceId: string | undefined = undefined;
|
||||
if (workspaceInviteHash) {
|
||||
@ -48,7 +56,7 @@ export class GoogleAuthController {
|
||||
workspaceId = workspace.id;
|
||||
}
|
||||
|
||||
const user = await this.userService.createUser(
|
||||
let user = await this.userService.createUser(
|
||||
{
|
||||
data: {
|
||||
email,
|
||||
@ -65,6 +73,37 @@ export class GoogleAuthController {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!user.avatarUrl) {
|
||||
let imagePath: string | undefined = undefined;
|
||||
|
||||
if (picture) {
|
||||
// Get image buffer from url
|
||||
const buffer = await getImageBufferFromUrl(picture);
|
||||
|
||||
// Extract mimetype and extension from buffer
|
||||
const type = await FileType.fromBuffer(buffer);
|
||||
|
||||
// Upload image
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename: `${uuidV4()}.${type?.ext}`,
|
||||
mimeType: type?.mime,
|
||||
fileFolder: FileFolder.ProfilePicture,
|
||||
});
|
||||
|
||||
imagePath = paths[0];
|
||||
}
|
||||
|
||||
user = await this.userService.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
avatarUrl: imagePath,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loginToken = await this.tokenService.generateLoginToken(user.email);
|
||||
|
||||
return res.redirect(this.tokenService.computeRedirectURI(loginToken.token));
|
||||
|
||||
@ -11,6 +11,7 @@ export type GoogleRequest = Request & {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
email: string;
|
||||
picture: string | null;
|
||||
workspaceInviteHash?: string;
|
||||
};
|
||||
};
|
||||
@ -45,16 +46,17 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
profile: any,
|
||||
done: VerifyCallback,
|
||||
): Promise<void> {
|
||||
const { name, emails } = profile;
|
||||
const { name, emails, photos } = profile;
|
||||
const state =
|
||||
typeof request.query.state === 'string'
|
||||
? JSON.parse(request.query.state)
|
||||
: undefined;
|
||||
|
||||
const user = {
|
||||
const user: GoogleRequest['user'] = {
|
||||
email: emails[0].value,
|
||||
firstName: name.givenName,
|
||||
lastName: name.familyName,
|
||||
picture: photos?.[0]?.value,
|
||||
workspaceInviteHash: state.workspaceInviteHash,
|
||||
};
|
||||
done(null, user);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const cropRegex = /([w|h])([0-9]+)/;
|
||||
|
||||
export type ShortCropSize = `${'w' | 'h'}${number}` | 'original';
|
||||
@ -19,3 +21,11 @@ export const getCropSize = (value: ShortCropSize): CropSize | null => {
|
||||
value: +match[2],
|
||||
};
|
||||
};
|
||||
|
||||
export const getImageBufferFromUrl = async (url: string): Promise<Buffer> => {
|
||||
const response = await axios.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
return Buffer.from(response.data, 'binary');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user