From 6e44a1a887335ac2dd90dc6fca9092d511bdadb1 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:13:31 -0300 Subject: [PATCH 01/20] feat: create upload file pipe --- src/globals/uploadFiles.pipe.ts | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/globals/uploadFiles.pipe.ts diff --git a/src/globals/uploadFiles.pipe.ts b/src/globals/uploadFiles.pipe.ts new file mode 100644 index 0000000..5f39095 --- /dev/null +++ b/src/globals/uploadFiles.pipe.ts @@ -0,0 +1,44 @@ +import { + FileTypeValidator, + Injectable, + MaxFileSizeValidator, + PipeTransform, + UnprocessableEntityException, +} from '@nestjs/common'; +import { CustomHttpError } from '../globals/responses/exceptions'; + +@Injectable() +export class CustomUploadFilePipe implements PipeTransform { + transform(file: any) { + console.log('file pipe: '); + console.log(file); + if (!file) { + throw new CustomHttpError('Nenhum arquivo foi enviado', 400); + } + + const fileTypeValidator = new FileTypeValidator({ + fileType: '^(image/png|image/jpeg|image/jpg|image/svg)$', + }); + + const isValidType = fileTypeValidator.isValid(file); + + if (!isValidType) { + throw new UnprocessableEntityException( + 'O tipo de arquivo enviado não é suportado. Apenas imagens nos formatos PNG, JPEG, JPG e SVG são aceitas.', + ); + } + + const maxSizeValidator = new MaxFileSizeValidator({ + maxSize: 20000, + }); + const isValidSize = maxSizeValidator.isValid(file); + + if (!isValidSize) { + throw new UnprocessableEntityException( + 'O arquivo excede o tamanho máximo permitido de 20KB.', + ); + } + + return file; + } +} From 7460c1ab0e92cb47de59347c683edcc333fca9e2 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:21:33 -0300 Subject: [PATCH 02/20] chore: update database --- .../migration.sql | 8 ++++ .../migration.sql | 2 + .../migration.sql | 8 ++++ prisma/schema.prisma | 41 +++++++++++-------- 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 prisma/migrations/20241115164805_add_profile_image_url_profile_enum/migration.sql create mode 100644 prisma/migrations/20241115170112_remove_profile_enum/migration.sql create mode 100644 prisma/migrations/20241115174852_add_profile_fiels/migration.sql diff --git a/prisma/migrations/20241115164805_add_profile_image_url_profile_enum/migration.sql b/prisma/migrations/20241115164805_add_profile_image_url_profile_enum/migration.sql new file mode 100644 index 0000000..3938fca --- /dev/null +++ b/prisma/migrations/20241115164805_add_profile_image_url_profile_enum/migration.sql @@ -0,0 +1,8 @@ +-- CreateEnum +CREATE TYPE "Profile" AS ENUM ('parent', 'child'); + +-- AlterTable +ALTER TABLE "children_profiles" ADD COLUMN "profileImageUrl" TEXT; + +-- AlterTable +ALTER TABLE "parent_profiles" ADD COLUMN "profileImageUrl" TEXT; diff --git a/prisma/migrations/20241115170112_remove_profile_enum/migration.sql b/prisma/migrations/20241115170112_remove_profile_enum/migration.sql new file mode 100644 index 0000000..8e29a32 --- /dev/null +++ b/prisma/migrations/20241115170112_remove_profile_enum/migration.sql @@ -0,0 +1,2 @@ +-- DropEnum +DROP TYPE "Profile"; diff --git a/prisma/migrations/20241115174852_add_profile_fiels/migration.sql b/prisma/migrations/20241115174852_add_profile_fiels/migration.sql new file mode 100644 index 0000000..cfb8b9d --- /dev/null +++ b/prisma/migrations/20241115174852_add_profile_fiels/migration.sql @@ -0,0 +1,8 @@ +-- CreateEnum +CREATE TYPE "Profile" AS ENUM ('parent', 'child'); + +-- AlterTable +ALTER TABLE "children_profiles" ADD COLUMN "profile" "Profile" NOT NULL DEFAULT 'child'; + +-- AlterTable +ALTER TABLE "parent_profiles" ADD COLUMN "profile" "Profile" NOT NULL DEFAULT 'parent'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6e5af88..e0564d2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,27 +47,31 @@ model ResetPasswordInfo { } model ParentProfile { - id String @id @default(uuid()) - fullname String - childrens ChildrenProfile[] - credentialId String @unique @map("credential_id") - credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade) - updatedAt DateTime @updatedAt @map("updated_at") - createdAt DateTime @default(now()) @map("created_at") + id String @id @default(uuid()) + profile Profile @default(parent) + fullname String + childrens ChildrenProfile[] + credentialId String @unique @map("credential_id") + credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade) + profileImageUrl String? + updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") @@map("parent_profiles") } model ChildrenProfile { - id Int @id @default(autoincrement()) - fullname String - birthdate DateTime - gender Genders - tasks Task[] - parentId String @map("parent_id") - parent ParentProfile @relation(fields: [parentId], references: [id], onDelete: Cascade) - updatedAt DateTime @updatedAt @map("updated_at") - createdAt DateTime @default(now()) @map("created_at") + id Int @id @default(autoincrement()) + profile Profile @default(child) + fullname String + birthdate DateTime + gender Genders + tasks Task[] + parentId String @map("parent_id") + parent ParentProfile @relation(fields: [parentId], references: [id], onDelete: Cascade) + profileImageUrl String? + updatedAt DateTime @updatedAt @map("updated_at") + createdAt DateTime @default(now()) @map("created_at") @@map("children_profiles") } @@ -132,3 +136,8 @@ enum Genders { male female } + +enum Profile { + parent + child +} From d757cb4d4d1a5202b23b6e8d36253b3d8147d0de Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:31:56 -0300 Subject: [PATCH 03/20] feat: create upload file dtos --- src/modules/account/account.dto.ts | 49 ++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/modules/account/account.dto.ts b/src/modules/account/account.dto.ts index e2aa511..1125aa0 100644 --- a/src/modules/account/account.dto.ts +++ b/src/modules/account/account.dto.ts @@ -1,24 +1,24 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { Genders } from '@prisma/client'; +import { Transform } from 'class-transformer'; import { - IsNotEmpty, - IsEmail, - IsStrongPassword, IsBoolean, - IsString, - IsEnum, IsDateString, + IsEmail, + IsEnum, + IsNotEmpty, + IsString, + IsStrongPassword, Matches, MinLength, } from 'class-validator'; -import { ApiProperty, PickType } from '@nestjs/swagger'; -import { Genders } from '@prisma/client'; +import { IsDateFormat, IsFullname } from 'src/decorators'; import { birthDateRegExp, fullnameRegExp, recoveryTokenRegExp, } from 'src/globals/constants'; import { messages } from 'src/globals/responses/validation'; -import { Transform } from 'class-transformer'; -import { IsDateFormat, IsFullname } from 'src/decorators'; export class CreateAccountDto { @ApiProperty({ example: 'email@example.com' }) @@ -88,3 +88,34 @@ export class LoginDto extends PickType(CreateAccountDto, [ 'email', 'password', ]) {} + +export class UploadFileDto { + @ApiProperty({ description: 'Nome do campo do arquivo', example: 'file' }) + readonly fieldname: string; + + @ApiProperty({ + description: 'Nome original do arquivo', + example: 'image.png', + }) + readonly originalname: string; + + @ApiProperty({ + description: 'Tipo MIME do arquivo', + example: 'image/png', + }) + readonly mimetype: string; + + @ApiProperty({ description: 'Conteúdo do arquivo em buffer' }) + readonly buffer: Buffer; + + @ApiProperty({ description: 'Tamanho do arquivo em bytes', example: 1024 }) + size: number; +} + +export class UploadFileIdDto { + @ApiProperty() + parentId: string; + + @ApiProperty({ example: 1 }) + childrenId: number; +} From b6ba2aa3591e7fff0b25dcb1ff4b3a41fd54cdbe Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:32:20 -0300 Subject: [PATCH 04/20] feat: create upload file controller --- src/modules/account/account.controller.ts | 59 +++++++++++++++++++---- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/modules/account/account.controller.ts b/src/modules/account/account.controller.ts index 6134a69..f25f006 100644 --- a/src/modules/account/account.controller.ts +++ b/src/modules/account/account.controller.ts @@ -1,27 +1,34 @@ import { - Controller, - Post, Body, + Controller, + Get, HttpCode, + Post, Put, - Get, + UploadedFile, UseGuards, + UseInterceptors, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { MailService } from '../mail/mail.service'; -import { AccountService } from './account.service'; import { responses } from 'src/globals/responses/docs'; -import { RecoveryControllerOutput } from './account.entity'; +import { User } from '../../decorators/account.decorator'; +import { CustomHttpError } from '../../globals/responses/exceptions'; +import { CustomUploadFilePipe } from '../../globals/uploadFiles.pipe'; +import { AuthorizationGuard, RequestToken } from '../../guard'; +import { MailService } from '../mail/mail.service'; import { + AccessTokenDto, CreateAccountDto, LoginDto, - AccessTokenDto, ResetPasswordDto, SetPasswordDto, + UploadFileDto, + UploadFileIdDto, } from './account.dto'; -import { AuthorizationGuard, RequestToken } from '../../guard'; -import { User } from '../../decorators/account.decorator'; +import { RecoveryControllerOutput } from './account.entity'; +import { AccountService } from './account.service'; @Controller('/auth') export class AccountController { @@ -88,6 +95,40 @@ export class AccountController { }; } + @Post('/upload-file') + @HttpCode(200) + @ApiTags('Upload Children Profile Image') + @ApiResponse(responses.ok) + @ApiResponse(responses.badRequest) + @ApiResponse(responses.internalError) + @UseInterceptors(FileInterceptor('file')) + async uploadFileChildren( + @UploadedFile(CustomUploadFilePipe) file: UploadFileDto, + @Body() { childrenId, parentId }: UploadFileIdDto, + ) { + if (!file) throw new CustomHttpError('Nenhum arquivo foi enviado', 400); + + let profile = 'parent'; + if (childrenId) profile = 'children'; + + const upload = await this.accountService.uploadFile( + file, + profile, + childrenId, + parentId, + ); + + if (!upload) + throw new CustomHttpError('Erro ao carregar foto de perfil', 400); + + return { + message: `Foto de perfil carregada com sucesso`, + data: { + upload, + }, + }; + } + @Post('/recovery') @HttpCode(200) @ApiTags('Reset Password') From 0c64de85549b100f5f2ebf6f77b211873b080ded Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:33:15 -0300 Subject: [PATCH 05/20] feat: create upload file service --- src/modules/account/account.service.ts | 61 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index 4149607..60b6b6c 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -1,31 +1,33 @@ -import { randomBytes } from 'node:crypto'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { AccountRepository } from './account.repository'; +import { JwtService, JwtSignOptions } from '@nestjs/jwt'; +import * as bcrypt from 'bcrypt'; +import { randomBytes } from 'node:crypto'; +import { + CustomHttpError, + EmailNotFoundException, + ExpiredRecoveryTokenException, + InvalidCredentialsException, + PoliciesException, + TryingEncryptException, + TryingHashException, +} from 'src/globals/responses/exceptions'; +import { encryptDataAsync, hashDataAsync } from 'src/globals/utils'; import { - CreateAccountDto, AccessTokenDto, + CreateAccountDto, ResetPasswordDto, SetPasswordDto, + UploadFileDto, } from './account.dto'; -import { encryptDataAsync, hashDataAsync } from 'src/globals/utils'; import { - PasswordResetOutput, - RandomTokenProps, - RandomTokenOutput, FormatLinkProps, iAuthTokenSubject, + PasswordResetOutput, + RandomTokenOutput, + RandomTokenProps, } from './account.entity'; -import { - EmailNotFoundException, - ExpiredRecoveryTokenException, - InvalidCredentialsException, - PoliciesException, - TryingEncryptException, - TryingHashException, -} from 'src/globals/responses/exceptions'; -import { JwtService, JwtSignOptions } from '@nestjs/jwt'; -import * as bcrypt from 'bcrypt'; +import { AccountRepository } from './account.repository'; @Injectable() export class AccountService { @@ -276,12 +278,35 @@ export class AccountService { await this.accountRepository.invalidateToken(existingToken.accessToken); } + async uploadFile( + file: UploadFileDto, + profile: string, + childrenId: any, + parentCredential: string, + ) { + try { + const pathFile = `${parentCredential}/${profile}/${file.originalname}`; + const data = await this.accountRepository.uploadFile(file, pathFile); + await this.accountRepository.saveProfileImage( + childrenId, + parentCredential, + data.fullPath, + ); + + return data; + } catch (error) { + throw new CustomHttpError( + 'Erro ao fazer upload do arquivo', + error.status || 500, + ); + } + } + async getCredential(id: string) { const credential = await this.accountRepository.getCredentialId(id); if (!credential) { throw new EmailNotFoundException(); } - return { credential, }; From af72b1da22e4fa410016053229250a92a52eda95 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Mon, 18 Nov 2024 18:34:17 -0300 Subject: [PATCH 06/20] feat: create saveProfileImage + uploadFile repository --- src/modules/account/account.repository.ts | 61 +++++++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/modules/account/account.repository.ts b/src/modules/account/account.repository.ts index 46aa516..2f68db4 100644 --- a/src/modules/account/account.repository.ts +++ b/src/modules/account/account.repository.ts @@ -1,16 +1,18 @@ import { Injectable } from '@nestjs/common'; +import { createClient } from '@supabase/supabase-js'; +import { handleErrors } from 'src/globals/errors'; import { PrismaService } from 'src/prisma/prisma.service'; +import { UploadFileDto } from './account.dto'; import { - NewAccountRepositoryInput, + GetCredential, GetCredentialIdByEmailOutput, - PasswordResetInput, getCredentialIdByRecoveryTokenInput, getCredentialIdByRecoveryTokenOutout, - SavePasswordInput, - GetCredential, + NewAccountRepositoryInput, + PasswordResetInput, SaveAccessTokenInput, + SavePasswordInput, } from './account.entity'; -import { handleErrors } from 'src/globals/errors'; @Injectable() export class AccountRepository { constructor(private prisma: PrismaService) {} @@ -220,4 +222,53 @@ export class AccountRepository { }) .catch((error) => handleErrors(error)); } + + supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY); + bucket = process.env.SUPABASE_BUCKET; + + async uploadFile(file: UploadFileDto, pathFile: string) { + try { + const data = await this.supabase.storage + .from(this.bucket) + .upload(pathFile, file.buffer, { + contentType: file.mimetype, + upsert: true, + }); + + return data.data; + } catch (error) {} + } + + async saveProfileImage( + childrenId: number, + parentCredential: string, + path: string, + ) { + try { + if (childrenId) { + const idNum = Number(childrenId); + await this.prisma.childrenProfile.update({ + where: { id: idNum }, + data: { profileImageUrl: path }, + }); + } else { + const parentId = await this.prisma.credential.findUnique({ + where: { email: parentCredential }, + select: { + parentProfile: { select: { id: true } }, + }, + }); + await this.prisma.parentProfile.update({ + where: { + id: parentId.parentProfile.id, + }, + data: { + profileImageUrl: path, + }, + }); + } + } catch (error) { + throw new Error('Erro ao salvar imagem ' + error); + } + } } From ee3006bbbe5613524f3431fd4720f06ab94a5dac Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:06:13 -0300 Subject: [PATCH 07/20] refactor: change parentId variable name --- src/modules/account/account.controller.ts | 4 ++-- src/modules/account/account.dto.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/account/account.controller.ts b/src/modules/account/account.controller.ts index f25f006..20e37f5 100644 --- a/src/modules/account/account.controller.ts +++ b/src/modules/account/account.controller.ts @@ -104,7 +104,7 @@ export class AccountController { @UseInterceptors(FileInterceptor('file')) async uploadFileChildren( @UploadedFile(CustomUploadFilePipe) file: UploadFileDto, - @Body() { childrenId, parentId }: UploadFileIdDto, + @Body() { childrenId, parentCredential }: UploadFileIdDto, ) { if (!file) throw new CustomHttpError('Nenhum arquivo foi enviado', 400); @@ -115,7 +115,7 @@ export class AccountController { file, profile, childrenId, - parentId, + parentCredential, ); if (!upload) diff --git a/src/modules/account/account.dto.ts b/src/modules/account/account.dto.ts index 1125aa0..68ba57b 100644 --- a/src/modules/account/account.dto.ts +++ b/src/modules/account/account.dto.ts @@ -114,7 +114,7 @@ export class UploadFileDto { export class UploadFileIdDto { @ApiProperty() - parentId: string; + parentCredential: string; @ApiProperty({ example: 1 }) childrenId: number; From 32d999a370f060ed44de12da11165b2255e45429 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:09:07 -0300 Subject: [PATCH 08/20] refactor: change childrenId type --- src/modules/account/account.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index 60b6b6c..ca21303 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -281,7 +281,7 @@ export class AccountService { async uploadFile( file: UploadFileDto, profile: string, - childrenId: any, + childrenId: number, parentCredential: string, ) { try { From c991145f81ed119f54ed12e1df46f6fabc7770b8 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:09:37 -0300 Subject: [PATCH 09/20] chore: drop profile enum --- .../migration.sql | 15 +++++++++++++++ prisma/schema.prisma | 7 ------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20241127170118_drop_profile_column/migration.sql diff --git a/prisma/migrations/20241127170118_drop_profile_column/migration.sql b/prisma/migrations/20241127170118_drop_profile_column/migration.sql new file mode 100644 index 0000000..64b8cce --- /dev/null +++ b/prisma/migrations/20241127170118_drop_profile_column/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `profile` on the `children_profiles` table. All the data in the column will be lost. + - You are about to drop the column `profile` on the `parent_profiles` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "children_profiles" DROP COLUMN "profile"; + +-- AlterTable +ALTER TABLE "parent_profiles" DROP COLUMN "profile"; + +-- DropEnum +DROP TYPE "Profile"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e0564d2..551f4eb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -48,7 +48,6 @@ model ResetPasswordInfo { model ParentProfile { id String @id @default(uuid()) - profile Profile @default(parent) fullname String childrens ChildrenProfile[] credentialId String @unique @map("credential_id") @@ -62,7 +61,6 @@ model ParentProfile { model ChildrenProfile { id Int @id @default(autoincrement()) - profile Profile @default(child) fullname String birthdate DateTime gender Genders @@ -136,8 +134,3 @@ enum Genders { male female } - -enum Profile { - parent - child -} From 43bf09495528f73c3625ec7dcc3c0d0c9d7ad152 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:23:54 -0300 Subject: [PATCH 10/20] feat: create new path file for children img --- src/modules/account/account.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index ca21303..f1a3ac3 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -285,7 +285,9 @@ export class AccountService { parentCredential: string, ) { try { - const pathFile = `${parentCredential}/${profile}/${file.originalname}`; + let pathFile = `${parentCredential}/${profile}/${file.originalname}`; + if (childrenId) + pathFile = `${parentCredential}/${profile}/${childrenId}/${file.originalname}`; const data = await this.accountRepository.uploadFile(file, pathFile); await this.accountRepository.saveProfileImage( childrenId, From d5a95edc2b32babf26d9c200a498fc3938aa857b Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:24:45 -0300 Subject: [PATCH 11/20] refactor: remove logs --- src/globals/uploadFiles.pipe.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/globals/uploadFiles.pipe.ts b/src/globals/uploadFiles.pipe.ts index 5f39095..de4cd2c 100644 --- a/src/globals/uploadFiles.pipe.ts +++ b/src/globals/uploadFiles.pipe.ts @@ -10,8 +10,6 @@ import { CustomHttpError } from '../globals/responses/exceptions'; @Injectable() export class CustomUploadFilePipe implements PipeTransform { transform(file: any) { - console.log('file pipe: '); - console.log(file); if (!file) { throw new CustomHttpError('Nenhum arquivo foi enviado', 400); } From 8407842194cd09603bc9e6b6521426f5255a7d3a Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:25:24 -0300 Subject: [PATCH 12/20] chore: add validation for parentCredential --- src/modules/account/account.dto.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/account/account.dto.ts b/src/modules/account/account.dto.ts index 68ba57b..b6bd710 100644 --- a/src/modules/account/account.dto.ts +++ b/src/modules/account/account.dto.ts @@ -114,6 +114,8 @@ export class UploadFileDto { export class UploadFileIdDto { @ApiProperty() + @IsNotEmpty() + @IsString({ message: messages.string }) parentCredential: string; @ApiProperty({ example: 1 }) From 83dddaa5af99cfbb9c22f4ea4c29f1bcaf8b3320 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:26:10 -0300 Subject: [PATCH 13/20] build: install supabase --- package.json | 1 + yarn.lock | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/package.json b/package.json index bbf91ec..b8a34aa 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@nestjs/swagger": "7.1.8", "@nestjs/throttler": "^6.2.1", "@prisma/client": "5.10.1", + "@supabase/supabase-js": "^2.46.1", "bcrypt": "5.1.1", "class-transformer": "0.5.1", "class-validator": "0.14.0", diff --git a/yarn.lock b/yarn.lock index 4523018..4e852f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1277,6 +1277,63 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@supabase/auth-js@2.65.1": + version "2.65.1" + resolved "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz" + integrity sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/functions-js@2.4.3": + version "2.4.3" + resolved "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz" + integrity sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/node-fetch@^2.6.14", "@supabase/node-fetch@2.6.15": + version "2.6.15" + resolved "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz" + integrity sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ== + dependencies: + whatwg-url "^5.0.0" + +"@supabase/postgrest-js@1.16.3": + version "1.16.3" + resolved "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.3.tgz" + integrity sha512-HI6dsbW68AKlOPofUjDTaosiDBCtW4XAm0D18pPwxoW3zKOE2Ru13Z69Wuys9fd6iTpfDViNco5sgrtnP0666A== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/realtime-js@2.10.7": + version "2.10.7" + resolved "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz" + integrity sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA== + dependencies: + "@supabase/node-fetch" "^2.6.14" + "@types/phoenix" "^1.5.4" + "@types/ws" "^8.5.10" + ws "^8.14.2" + +"@supabase/storage-js@2.7.1": + version "2.7.1" + resolved "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz" + integrity sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/supabase-js@^2.46.1": + version "2.46.1" + resolved "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.46.1.tgz" + integrity sha512-HiBpd8stf7M6+tlr+/82L8b2QmCjAD8ex9YdSAKU+whB/SHXXJdus1dGlqiH9Umy9ePUuxaYmVkGd9BcvBnNvg== + dependencies: + "@supabase/auth-js" "2.65.1" + "@supabase/functions-js" "2.4.3" + "@supabase/node-fetch" "2.6.15" + "@supabase/postgrest-js" "1.16.3" + "@supabase/realtime-js" "2.10.7" + "@supabase/storage-js" "2.7.1" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" @@ -1491,6 +1548,11 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/phoenix@^1.5.4": + version "1.6.5" + resolved "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz" + integrity sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w== + "@types/prop-types@*": version "15.7.12" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" @@ -1572,6 +1634,13 @@ resolved "https://registry.npmjs.org/@types/validator/-/validator-13.11.2.tgz" integrity sha512-nIKVVQKT6kGKysnNt+xLobr+pFJNssJRi2s034wgWeFBUx01fI8BeHTW2TcRp7VcFu9QCYG8IlChTuovcm0oKQ== +"@types/ws@^8.5.10": + version "8.5.13" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.1" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz" @@ -8378,6 +8447,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^8.14.2: + version "8.18.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xregexp@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz" From cdb0ef32be4893939d686daf2dec121e442e64e4 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:30:43 -0300 Subject: [PATCH 14/20] refactor: remove unnecessary validation --- src/modules/account/account.controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/account/account.controller.ts b/src/modules/account/account.controller.ts index 20e37f5..0f23712 100644 --- a/src/modules/account/account.controller.ts +++ b/src/modules/account/account.controller.ts @@ -97,7 +97,7 @@ export class AccountController { @Post('/upload-file') @HttpCode(200) - @ApiTags('Upload Children Profile Image') + @ApiTags('Upload Profile Image') @ApiResponse(responses.ok) @ApiResponse(responses.badRequest) @ApiResponse(responses.internalError) @@ -106,8 +106,6 @@ export class AccountController { @UploadedFile(CustomUploadFilePipe) file: UploadFileDto, @Body() { childrenId, parentCredential }: UploadFileIdDto, ) { - if (!file) throw new CustomHttpError('Nenhum arquivo foi enviado', 400); - let profile = 'parent'; if (childrenId) profile = 'children'; From 28c9f075901c31aeaff8c17340f26305799fd24e Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 27 Nov 2024 14:44:38 -0300 Subject: [PATCH 15/20] feat: create path file func --- src/globals/utils.ts | 14 ++++++++++++++ src/modules/account/account.service.ts | 15 +++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/globals/utils.ts b/src/globals/utils.ts index 455c3b9..4c9abf4 100644 --- a/src/globals/utils.ts +++ b/src/globals/utils.ts @@ -1,6 +1,7 @@ import { createHash } from 'node:crypto'; import type { HashDataAsyncProps, EncryptDataAsyncProps } from './entity'; import * as bcrypt from 'bcrypt'; +import { UploadFileDto } from '../modules/account/account.dto'; const algorithm = 'sha256'; const digest = 'hex'; @@ -49,3 +50,16 @@ export function encryptDataAsync( }); }); } + +export function createFilePath( + file: UploadFileDto, + profile: string, + childrenId: number, + parentCredential: string, +) { + let pathFile = `${parentCredential}/${profile}/${file.originalname}`; + if (childrenId) + pathFile = `${parentCredential}/${profile}/${childrenId}/${file.originalname}`; + + return pathFile; +} diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index f1a3ac3..7c1f3f4 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -12,7 +12,11 @@ import { TryingEncryptException, TryingHashException, } from 'src/globals/responses/exceptions'; -import { encryptDataAsync, hashDataAsync } from 'src/globals/utils'; +import { + createFilePath, + encryptDataAsync, + hashDataAsync, +} from 'src/globals/utils'; import { AccessTokenDto, CreateAccountDto, @@ -285,9 +289,12 @@ export class AccountService { parentCredential: string, ) { try { - let pathFile = `${parentCredential}/${profile}/${file.originalname}`; - if (childrenId) - pathFile = `${parentCredential}/${profile}/${childrenId}/${file.originalname}`; + const pathFile = createFilePath( + file, + profile, + childrenId, + parentCredential, + ); const data = await this.accountRepository.uploadFile(file, pathFile); await this.accountRepository.saveProfileImage( childrenId, From f19328e1e3846c41cbd0f0bdbdcd63a170c6a6d4 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 4 Dec 2024 14:57:15 -0300 Subject: [PATCH 16/20] docs: update .env.example --- .env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.example b/.env.example index e8d4aed..369bc02 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,10 @@ SALT_DATA_PASS= SECRET_JWT= +SUPABASE_URL= +SUPABASE_KEY= +SUPABASE_BUCKET= + MAIL_PORT= MAIL_HOST= MAIL_USER= From 2e163d0851b45269aaecf27b603e9224fb3efb39 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 4 Dec 2024 15:08:49 -0300 Subject: [PATCH 17/20] feat: get public url image --- src/modules/account/account.repository.ts | 8 ++++++-- src/modules/account/account.service.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/modules/account/account.repository.ts b/src/modules/account/account.repository.ts index 2f68db4..35baf5e 100644 --- a/src/modules/account/account.repository.ts +++ b/src/modules/account/account.repository.ts @@ -228,14 +228,18 @@ export class AccountRepository { async uploadFile(file: UploadFileDto, pathFile: string) { try { - const data = await this.supabase.storage + await this.supabase.storage .from(this.bucket) .upload(pathFile, file.buffer, { contentType: file.mimetype, upsert: true, }); - return data.data; + const url = this.supabase.storage + .from(this.bucket) + .getPublicUrl(pathFile); + + return url.data; } catch (error) {} } diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index 7c1f3f4..457c299 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -299,7 +299,7 @@ export class AccountService { await this.accountRepository.saveProfileImage( childrenId, parentCredential, - data.fullPath, + data.publicUrl, ); return data; From dd95ffab7414b41619ed29ce80b2da0ace578ee0 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 4 Dec 2024 15:23:35 -0300 Subject: [PATCH 18/20] feat: create get ids functions --- src/modules/account/account.repository.ts | 29 +++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/modules/account/account.repository.ts b/src/modules/account/account.repository.ts index 35baf5e..1c535b3 100644 --- a/src/modules/account/account.repository.ts +++ b/src/modules/account/account.repository.ts @@ -256,15 +256,10 @@ export class AccountRepository { data: { profileImageUrl: path }, }); } else { - const parentId = await this.prisma.credential.findUnique({ - where: { email: parentCredential }, - select: { - parentProfile: { select: { id: true } }, - }, - }); + const parentId = await this.getParentId(parentCredential); await this.prisma.parentProfile.update({ where: { - id: parentId.parentProfile.id, + id: parentId, }, data: { profileImageUrl: path, @@ -275,4 +270,24 @@ export class AccountRepository { throw new Error('Erro ao salvar imagem ' + error); } } + + async getChildrenId(parentCredential: string) { + const parentId = await this.getParentId(parentCredential); + const childrenId = await this.prisma.childrenProfile.findMany({ + where: { parentId }, + }); + + return childrenId; + } + + async getParentId(email: string) { + const parentId = await this.prisma.credential.findUnique({ + where: { email }, + select: { + parentProfile: { select: { id: true } }, + }, + }); + + return parentId.parentProfile.id; + } } From 3bf5a5066f99b00a5ccfcad6b1ddd53004100f36 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 4 Dec 2024 15:32:47 -0300 Subject: [PATCH 19/20] refactor: make childrenId a number --- src/modules/account/account.controller.ts | 5 +++-- src/modules/account/account.repository.ts | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/account/account.controller.ts b/src/modules/account/account.controller.ts index 0f23712..f4f2c7d 100644 --- a/src/modules/account/account.controller.ts +++ b/src/modules/account/account.controller.ts @@ -106,13 +106,14 @@ export class AccountController { @UploadedFile(CustomUploadFilePipe) file: UploadFileDto, @Body() { childrenId, parentCredential }: UploadFileIdDto, ) { + const childrenIdNum = Number(childrenId); let profile = 'parent'; - if (childrenId) profile = 'children'; + if (childrenIdNum) profile = 'children'; const upload = await this.accountService.uploadFile( file, profile, - childrenId, + childrenIdNum, parentCredential, ); diff --git a/src/modules/account/account.repository.ts b/src/modules/account/account.repository.ts index 1c535b3..d59a25a 100644 --- a/src/modules/account/account.repository.ts +++ b/src/modules/account/account.repository.ts @@ -250,9 +250,8 @@ export class AccountRepository { ) { try { if (childrenId) { - const idNum = Number(childrenId); await this.prisma.childrenProfile.update({ - where: { id: idNum }, + where: { id: childrenId }, data: { profileImageUrl: path }, }); } else { From 004a74e3ad41b50c9e1c72d1c5f1f7d35afc4df3 Mon Sep 17 00:00:00 2001 From: Kauane Santos Date: Wed, 4 Dec 2024 15:34:06 -0300 Subject: [PATCH 20/20] refactor: verify childrenId --- src/modules/account/account.service.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index 457c299..260219a 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -288,6 +288,19 @@ export class AccountService { childrenId: number, parentCredential: string, ) { + if (childrenId) { + const getChildrenId = await this.accountRepository.getChildrenId( + parentCredential, + ); + const childrenIds = getChildrenId.map((child) => { + return child.id; + }); + + if (!childrenIds.includes(childrenId)) { + throw new CustomHttpError('Id de criança inválido', 400); + } + } + try { const pathFile = createFilePath( file,