mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-28 09:12:37 +03:00
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:
parent
52399d4dde
commit
58530be78b
@ -54,6 +54,7 @@
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"file-type": "13.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-upload": "^13.0.0",
|
||||
|
@ -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');
|
||||
};
|
||||
|
@ -2559,6 +2559,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
|
||||
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
|
||||
|
||||
"@tokenizer/token@^0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.1.1.tgz#f0d92c12f87079ddfd1b29f614758b9696bc29e3"
|
||||
integrity sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==
|
||||
|
||||
"@ts-morph/common@~0.17.0":
|
||||
version "0.17.0"
|
||||
resolved "https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz"
|
||||
@ -5063,6 +5068,16 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-type@13.0.0:
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-13.0.0.tgz#00091b5b642f131d4cfe9f51192270e363e3d54c"
|
||||
integrity sha512-fuTZAd04cbjCOcv+Y9erYUw92G2n7DSsMhIWHNunQz5ajIlDs+yamZ7QgtHe+k3XSFnO0NOUiLq9AA8w6ut+9g==
|
||||
dependencies:
|
||||
readable-web-to-node-stream "^2.0.0"
|
||||
strtok3 "^5.0.1"
|
||||
token-types "^2.0.0"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
filename-reserved-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz"
|
||||
@ -5659,7 +5674,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ieee754@^1.1.13:
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@ -5951,6 +5966,11 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9:
|
||||
dependencies:
|
||||
which-typed-array "^1.1.11"
|
||||
|
||||
is-typedarray@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
|
||||
|
||||
is-unicode-supported@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz"
|
||||
@ -7341,6 +7361,11 @@ pause@0.0.1:
|
||||
resolved "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz"
|
||||
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
|
||||
|
||||
peek-readable@^3.1.0:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-3.1.4.tgz#f5c3b41a4eeb63a1322c4131f0b5bac7105b892e"
|
||||
integrity sha512-DX7ec7frSMtCWw+zMd27f66hcxIz/w9LQTY2RflB4WNHCVPAye1pJiP2t3gvaaOhu7IOhtPbHw8MemMj+F5lrg==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||
@ -7603,6 +7628,11 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-web-to-node-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz#751e632f466552ac0d5c440cc01470352f93c4b7"
|
||||
integrity sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA==
|
||||
|
||||
readdir-glob@^1.0.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz"
|
||||
@ -8148,6 +8178,15 @@ strnum@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
|
||||
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
|
||||
|
||||
strtok3@^5.0.1:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-5.0.2.tgz#bb81f1f56742e16f1a30ccce5dc3d9498aa5475a"
|
||||
integrity sha512-EFeVpFC5qDsqPEJSrIYyS/ueFBknGhgSK9cW+YAJF/cgJG/KSjoK7X6rK5xnpcLe7y1LVkVFCXWbAb+ClNKzKQ==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.1.1"
|
||||
debug "^4.1.1"
|
||||
peek-readable "^3.1.0"
|
||||
|
||||
subscriptions-transport-ws@0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz"
|
||||
@ -8381,6 +8420,14 @@ toidentifier@1.0.1:
|
||||
resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
token-types@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/token-types/-/token-types-2.1.1.tgz#bd585d64902aaf720b8979d257b4b850b4d45c45"
|
||||
integrity sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.1.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
||||
@ -8619,6 +8666,13 @@ typed-array-length@^1.0.4:
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
typedarray-to-buffer@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
||||
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user