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
144 changes: 104 additions & 40 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,135 @@
# Como Contribuir - Seu Passaporte de Entrada

Estamos felizes em receber você aqui e saber que está interessado em contribuir para o nosso projeto. Como um projeto de código aberto, cada contribuição é valorizada e ajuda a impulsionar o crescimento e a qualidade do nosso trabalho. Este guia foi criado para orientá-lo sobre como você pode participar e fazer parte da nossa comunidade de desenvolvimento. Estamos ansiosos para ver suas contribuições e trabalhar juntos para tornar nosso projeto ainda melhor!
Estamos felizes em receber você aqui e saber que está interessado em contribuir para o nosso projeto. Como um projeto de código aberto, cada contribuição é valorizada e ajuda a impulsionar o crescimento e a qualidade do nosso trabalho. Este guia foi criado para orientá-lo sobre como você pode participar e fazer parte da nossa comunidade de desenvolvimento.

---

## Código de Conduta

Para garantir um ambiente respeitável e inclusivo, leia e siga nosso [Código de Conduta](./CODE_OF_CONDUCT.md).

---

## Começando a Contribuir

Contribuir para o nosso projeto é fácil e estamos ansiosos para receber suas contribuições! Antes de entrarmos nos passos para instalação da aplicação, você precisará configurar algumas ferramentas e preparar seu ambiente de desenvolvimento.
Antes de iniciar, você precisará configurar seu ambiente de desenvolvimento.

Aqui está o que você precisa:
### Requisitos

- Uma conta no [GitHub](https://github.com/).
- O *version control system* [Git](https://git-scm.com/) instalado.
- Um IDE para o desenvolvimento. Recomendamos o [Visual Studio Code](https://code.visualstudio.com).
- O [Node.js v22.11.0](https://nodejs.org/en) ou superior.
- [MongoDB Community Server](https://www.mongodb.com/try/download/community).
- Uma conta no GitHub
- Git instalado
- IDE (recomendado: VS Code)
- Node.js v22.11.0 ou superior
- MongoDB Community Server

---

## Instalação

### 1. Clonar o Repositório

O primeiro passo é clonar o repositório do projeto para o seu ambiente local.
```bash
git clone https://github.com/Bug-Busters-F/ProDesk-backend
cd ProDesk-backend/backend
```

---

1. Abra um terminal.
### 2. Instalar Dependências e Variáveis de Ambiente

2. Execute o seguinte comando para clonar o repositório:
```bash
git clone https://github.com/Bug-Busters-F/ProDesk-backend
```
```bash
npm install
```

3. Navegue até o diretório do projeto:
```bash
cd ProDesk-backend\\backend
```
Copie o arquivo de variáveis:

### 2. Instalar Dependências e Variáveis de Ambiente
```bash
cp .env.example .env
```

Edite o `.env`:

```env
# DATABASE
MONGO_URI=mongodb://localhost:27017/prodesk

# APP
PORT=3000
NODE_ENV=development

# EMAIL (Gmail)
EMAIL_USER=seuemail@gmail.com
EMAIL_PASS=sua_senha_de_app
```

---

## Como configurar EMAIL_USER e EMAIL_PASS

Para que o envio de emails funcione (ex: recuperação de senha), é necessário configurar uma conta do Gmail com senha de aplicativo.

### Importante

- Não utilize sua senha normal do Gmail
- É obrigatório ter a verificação em duas etapas ativada

Com o ambiente configurado, basta instalar as dependências do Node.js e iniciar o servidor de desenvolvimento.
---

1. Instale as dependências do projeto:
```sh
npm install
```
### Passo a passo

2. Configure as variáveis de ambiente
1. Acesse:
https://myaccount.google.com/apppasswords

```sh
cp .env.example .env
```
2. Faça login na sua conta Google

3. Abra o arquivo `.env` e edite a conexão com o banco de dados.
3. Ative a verificação em duas etapas

```sh
# DATABASE
MONGO_URI=mongodb://localhost:27017/prodesk
4. Em Selecionar app, escolha: Mail

# APP
PORT=3000
NODE_ENV=development
```
5. Em Selecionar dispositivo, escolha: Outro (Personalizado)

### 3. Rodar o Projeto
6. Informe um nome, por exemplo: NestJS

Execute a aplicação em modo de desenvolvimento:
7. Clique em Gerar

```sh
---

### Resultado

O Google irá gerar um código semelhante a:

```
abcd efgh ijkl mnop
```

Copie esse código e utilize no `.env` sem espaços:

```env
EMAIL_PASS=abcdefghijklmnop
```

---

## 3. Rodar o Projeto

```bash
npm run start:dev
```
```

---

## Resultado esperado

- Aplicação rodando em http://localhost:3000
- Swagger disponível em http://localhost:3000/api
- Envio de email funcionando corretamente

## Primeiro acesso (usuário admin padrão)

Ao iniciar a aplicação pela primeira vez, um usuário administrador é criado automaticamente com as seguintes credenciais:

```json
{
"email": "admin@pro4tech.com",
"password": "Pro4Tech"
}
1 change: 1 addition & 0 deletions backend/src/modules/company/company.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface CompanyDetails {
id: string;
name: string;
cnpj: string;
logo?: string;
}
3 changes: 3 additions & 0 deletions backend/src/modules/company/company.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export class Company {

@Prop({ required: true, unique: true })
cnpj: string;

@Prop({ required: false })
logo?: string;
}

export const CompanySchema = SchemaFactory.createForClass(Company);
1 change: 1 addition & 0 deletions backend/src/modules/company/company.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class CompanyService {
id: company._id.toString(),
name: company.name,
cnpj: company.cnpj,
logo: company.logo,
};
}
}
113 changes: 112 additions & 1 deletion backend/src/modules/file/application/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { FileDocument } from '../infra/schemas/file.schema';
import { FileEntity } from '../domain/file.entity';
import { LocalStorage } from '../infra/storage/local.storage';
import { UserDocument } from '../../user/user.schema';
import * as fs from 'fs';
import { CompanyDocument } from '../../company/company.schema';

@Injectable()
export class FileService {
Expand All @@ -12,6 +15,12 @@ export class FileService {
private readonly fileModel:
Model<FileDocument>,

@InjectModel('User')
private readonly userModel: Model<UserDocument>,

@InjectModel('Company')
private readonly companyModel: Model<CompanyDocument>,

private readonly storage:
LocalStorage
) {}
Expand Down Expand Up @@ -50,6 +59,41 @@ export class FileService {
);
}

async uploadProfileImage(
file: Express.Multer.File,
userId: string
): Promise<FileEntity> {

const fileData = this.storage.save(
file,
`profile/${userId}`,
userId
);

const newFile = new this.fileModel({
filename: fileData.filename,
originalname: fileData.originalname,
mimetype: fileData.mimetype,
size: fileData.size,
path: fileData.path,
uploadedBy: fileData.uploadedBy
});

const savedFile = await newFile.save();

const user = await this.userModel.findById(userId);

if (user?.profileImage) {
this.storage.delete(user.profileImage);
}

await this.userModel.findByIdAndUpdate(userId, {
profileImage: fileData.path
});

return this._mapToEntity(savedFile);
}

private _mapToEntity(
file: any
): FileEntity {
Expand All @@ -68,4 +112,71 @@ export class FileService {
file.createdAt
};
}

async getProfileImage(userId: string): Promise<string | null> {
const user = await this.userModel.findById(userId);

if (!user || !user.profileImage) {
return null;
}

if (!fs.existsSync(user.profileImage)) {
return null;
}

return user.profileImage;
}

async uploadCompanyLogo(
file: Express.Multer.File,
companyId: string
): Promise<FileEntity> {

const fileData = this.storage.save(
file,
`company/${companyId}`,
companyId
);

const newFile = new this.fileModel({
filename: fileData.filename,
originalname: fileData.originalname,
mimetype: fileData.mimetype,
size: fileData.size,
path: fileData.path,
uploadedBy: companyId
});

const savedFile = await newFile.save();

const company = await this.companyModel.findById(companyId);


if (company?.logo) {
this.storage.delete(company.logo);
}

await this.companyModel.findByIdAndUpdate(companyId, {
logo: fileData.path
});

return this._mapToEntity(savedFile);
}

async getCompanyLogo(companyId: string): Promise<string | null> {
const company = await this.companyModel.findById(companyId);

if (!company || !company.logo) {
return null;
}

if (!fs.existsSync(company.logo)) {
await this.companyModel.findByIdAndUpdate(companyId, {
logo: null
});
return null;
}

return company.logo;
}
}
45 changes: 45 additions & 0 deletions backend/src/modules/file/config/profile-multer.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { diskStorage } from 'multer';
import { extname } from 'path';
import { BadRequestException } from '@nestjs/common';
import * as fs from 'fs';

export const profileMulterConfig = {
storage: diskStorage({
destination: (req: any, file, cb) => {
const userId = req.user?.id || 'temp';
const path = `./uploads/profile/${userId}`;

fs.mkdirSync(path, { recursive: true });

cb(null, path);
},

filename: (req, file, cb) => {
const uniqueSuffix =
Date.now() + '-' + Math.round(Math.random() * 1e9);

const extension = extname(file.originalname);

cb(null, `${uniqueSuffix}${extension}`);
},
}),

limits: {
fileSize: 2 * 1024 * 1024, // 2MB
},

fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/jpg'];

if (!allowed.includes(file.mimetype)) {
return cb(
new BadRequestException(
'Only image files (jpg, jpeg, png) are allowed',
),
false,
);
}

cb(null, true);
},
};
Loading
Loading