Skip to content
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ SALT_DATA_PASS=

SECRET_JWT=

SUPABASE_URL=
SUPABASE_KEY=
SUPABASE_BUCKET=

MAIL_PORT=
MAIL_HOST=
MAIL_USER=
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- DropEnum
DROP TYPE "Profile";
Original file line number Diff line number Diff line change
@@ -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';
15 changes: 15 additions & 0 deletions prisma/migrations/20241127170118_drop_profile_column/migration.sql
Original file line number Diff line number Diff line change
@@ -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";
34 changes: 18 additions & 16 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,29 @@ 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())
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())
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")
}
Expand Down
42 changes: 42 additions & 0 deletions src/globals/uploadFiles.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
FileTypeValidator,
Injectable,
MaxFileSizeValidator,
PipeTransform,
UnprocessableEntityException,
} from '@nestjs/common';
import { CustomHttpError } from '../globals/responses/exceptions';

@Injectable()
export class CustomUploadFilePipe implements PipeTransform {
transform(file: any) {
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;
}
}
14 changes: 14 additions & 0 deletions src/globals/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}
58 changes: 49 additions & 9 deletions src/modules/account/account.controller.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -88,6 +95,39 @@ export class AccountController {
};
}

@Post('/upload-file')
@HttpCode(200)
@ApiTags('Upload Profile Image')
@ApiResponse(responses.ok)
@ApiResponse(responses.badRequest)
@ApiResponse(responses.internalError)
@UseInterceptors(FileInterceptor('file'))
async uploadFileChildren(
@UploadedFile(CustomUploadFilePipe) file: UploadFileDto,
@Body() { childrenId, parentCredential }: UploadFileIdDto,
) {
const childrenIdNum = Number(childrenId);
let profile = 'parent';
if (childrenIdNum) profile = 'children';

const upload = await this.accountService.uploadFile(
file,
profile,
childrenIdNum,
parentCredential,
);

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')
Expand Down
51 changes: 42 additions & 9 deletions src/modules/account/account.dto.ts
Original file line number Diff line number Diff line change
@@ -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' })
Expand Down Expand Up @@ -88,3 +88,36 @@ 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()
@IsNotEmpty()
@IsString({ message: messages.string })
parentCredential: string;

@ApiProperty({ example: 1 })
childrenId: number;
}
Loading