Skip to content

Commit 9d45c6b

Browse files
Merge branch 'main' of https://github.com/nicolasreisdev/CTable into config/create-issues-smells/nicoals
2 parents e6b1c05 + 86533ed commit 9d45c6b

File tree

27 files changed

+1169
-223
lines changed

27 files changed

+1169
-223
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Construir um sistema de ponta a ponta que rastreia fóruns, processa o texto das
2020

2121
### Rode a migration
2222

23-
- Para criar o arquivo em databse.bd, no terminal, na pasta backend, execute: npx knex migrate:latest --knexfile knexfile.ts
23+
- Para criar o arquivo em databse.bd, no terminal, na pasta backend/src, execute: npx knex migrate:latest --knexfile knexfile.ts
2424

2525

2626
## Criar seeds (popular tabelas)

backend/package-lock.json

Lines changed: 13 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@
1515
"license": "ISC",
1616
"type": "commonjs",
1717
"devDependencies": {
18-
"@types/cors": "^2.8.19",
1918
"@types/bcryptjs": "^2.4.6",
20-
"@types/jest": "^30.0.0",
19+
"@types/cors": "^2.8.19",
2120
"@types/express": "^5.0.5",
21+
"@types/jest": "^30.0.0",
2222
"@types/jsonwebtoken": "^9.0.10",
2323
"@types/node": "^24.3.1",
2424
"@types/supertest": "^6.0.3",
2525
"jest": "^30.2.0",
2626
"supertest": "^7.1.4",
2727
"ts-jest": "^29.4.5",
28+
"ts-node": "^10.9.2",
2829
"ts-node-dev": "^2.0.0",
2930
"typescript": "^5.9.2"
3031
},
3132
"dependencies": {
32-
"cors": "^2.8.5",
3333
"bcryptjs": "^3.0.3",
34+
"cors": "^2.8.5",
3435
"express": "^5.1.0",
3536
"jsonwebtoken": "^9.0.2",
3637
"knex": "^3.1.0",

backend/src/business/businessLogicProject.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,72 @@ class businessLogicProject{
4242
});
4343
}
4444

45-
updateProject(){
45+
async updateProject(projectId: string, projectData: Partial<any>, userId: number) {
46+
47+
return knex.transaction(async (trx) => {
48+
49+
// Busca projeto e valida autoria
50+
const existingProject = await trx('Projects')
51+
.where({ projectID: projectId })
52+
.first();
53+
54+
if (!existingProject) {
55+
throw new Error("Projeto não encontrado.");
56+
}
57+
58+
if (existingProject.creatorID !== userId) {
59+
throw new Error("Você não tem permissão para editar este projeto.");
60+
}
61+
62+
// Prepara campos da tabela PRINCIPAL (Projects)
63+
const fieldsToUpdate: any = {};
64+
65+
if (projectData.title !== undefined) fieldsToUpdate.title = projectData.title;
66+
if (projectData.description !== undefined) fieldsToUpdate.description = projectData.description;
67+
if (projectData.status !== undefined) fieldsToUpdate.status = projectData.status;
68+
if (projectData.startDate !== undefined) fieldsToUpdate.startDate = projectData.startDate;
69+
70+
// Atualiza o 'updatedAt' se houver mudanças nos campos principais
71+
if (Object.keys(fieldsToUpdate).length > 0) {
72+
fieldsToUpdate.updatedAt = new Date();
73+
74+
await trx('Projects')
75+
.where({ projectID: projectId })
76+
.update(fieldsToUpdate);
77+
}
78+
79+
// Atualiza a tabela de relacionamento (ProjectsKeywords)
80+
if (projectData.technologies !== undefined) {
81+
82+
// Remove TODAS as associações antigas desse projeto
83+
await trx('ProjectsKeywords')
84+
.where({ projectID: projectId })
85+
.del();
86+
87+
// Se a nova lista não estiver vazia, insere as novas
88+
if (Array.isArray(projectData.technologies) && projectData.technologies.length > 0) {
89+
90+
// Busca os IDs das tags (Keywords) baseadas no nome (string) enviado pelo front
91+
const keywordIDs = await trx('Keywords')
92+
.whereIn('tag', projectData.technologies)
93+
.select('keywordID');
94+
95+
// Prepara o array de inserção
96+
const linksToInsert = keywordIDs.map((k: any) => ({
97+
projectID: projectId,
98+
keywordID: k.keywordID
99+
}));
100+
101+
// Insere
102+
if (linksToInsert.length > 0) {
103+
await trx('ProjectsKeywords').insert(linksToInsert);
104+
}
105+
}
106+
}
107+
108+
// Retorna o projeto atualizado
109+
return await trx('Projects').where({ projectID: projectId }).first();
110+
});
46111
}
47112

48113
async userProjects(creatorID: number){

backend/src/controller/requestController.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ class requestController {
9191
throw error;
9292
}
9393
}
94+
95+
async updateProject(projectId: string, data: projectData, userId: number){
96+
try{
97+
98+
const updatedProject = await businessLogicProject.updateProject(projectId, data, userId);
99+
100+
return updatedProject;
101+
102+
}catch(error){
103+
throw error;
104+
}
105+
}
94106
}
95107

96108
export default new requestController();

backend/src/routes.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,37 @@ routes.get('/api/user/projects', authMiddleware, async(request, response) => {
134134
}
135135
})
136136

137+
// Endpoint para atualizar um projeto existente
138+
routes.put('/api/user/updateproject/:projectId', authMiddleware, async(request, response) => {
139+
try{
140+
const { projectId } = request.params;
141+
const updatedData = request.body;
142+
const creatorID = request.user.id;
143+
144+
const updatedProject = await requestController.updateProject(projectId, updatedData, creatorID);
145+
146+
return response.status(200).json({
147+
message: "Projeto atualizado com sucesso!",
148+
project: updatedProject
149+
});
150+
151+
}catch(error){
152+
153+
if (error instanceof z.ZodError) {
154+
return response.status(400).json({
155+
message: "Erro de validação",
156+
errors: error.flatten().fieldErrors
157+
});
158+
}
159+
160+
if(error instanceof Error){
161+
return response.status(400).json({ message: error.message });
162+
}
163+
164+
console.error("Erro interno no servidor:", error);
165+
return response.status(500).json({ message: "Erro interno no servidor." });
166+
167+
}
168+
});
169+
137170
export default routes;

frontend/src/API/Auth.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
interface LoginProps {
1+
export interface LoginProps {
22
username: string;
33
senha: string;
44
}
@@ -18,11 +18,12 @@ export async function Login(data: LoginProps) {
1818
}
1919

2020
const {user, token} = await response.json();
21-
localStorage.setItem('token', token);
21+
console.log("Dados do usuário logado:", user);
22+
return { user, token };
2223

2324
}
2425

25-
interface RegisterProps {
26+
export interface RegisterProps {
2627
nomeCompleto: string;
2728
username: string;
2829
email: string;
@@ -44,4 +45,15 @@ export async function Register(data: RegisterProps) {
4445
const errorData = await response.json();
4546
throw new Error(errorData.message);
4647
}
48+
}
49+
50+
export function logout() {
51+
localStorage.removeItem('token');
52+
localStorage.removeItem('user');
53+
}
54+
55+
export function getCurrentUser() {
56+
const userString = localStorage.getItem('user');
57+
if (!userString) return null;
58+
return JSON.parse(userString);
4759
}

frontend/src/API/AuthContext.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { createContext, useContext, useState, useEffect } from 'react';
2+
import type { ReactNode } from 'react';
3+
import * as api from './Auth';
4+
import type { LoginProps } from './Auth';
5+
6+
interface User {
7+
id: string;
8+
username: string;
9+
nomeCompleto?: string;
10+
email?: string;
11+
}
12+
13+
interface AuthContextType {
14+
currentUser: User | null;
15+
login: (data: LoginProps) => Promise<void>;
16+
logout: () => void;
17+
isAuthenticated: boolean;
18+
}
19+
20+
// Crie o Contexto
21+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
22+
23+
// Crie o Provedor (Provider)
24+
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
25+
const [currentUser, setCurrentUser] = useState<User | null>(null);
26+
const [isLoading, setIsLoading] = useState(true);
27+
28+
// Ao carregar o app, tente buscar o usuário do localStorage
29+
useEffect(() => {
30+
try {
31+
const user = api.getCurrentUser();
32+
const token = localStorage.getItem('token');
33+
if (user && token) {
34+
setCurrentUser(user);
35+
}
36+
} catch (error) {
37+
console.error("Falha ao carregar usuário:", error);
38+
api.logout(); // Limpa dados corrompidos
39+
} finally {
40+
setIsLoading(false);
41+
}
42+
}, []);
43+
44+
// Função de login que atualiza o estado E o localStorage
45+
const login = async (data: LoginProps) => {
46+
const { user, token } = await api.Login(data); // Chama sua API
47+
localStorage.setItem('token', token);
48+
localStorage.setItem('user', JSON.stringify(user));
49+
setCurrentUser(user);
50+
};
51+
52+
// Função de logout que limpa tudo
53+
const logout = () => {
54+
api.logout();
55+
setCurrentUser(null);
56+
};
57+
58+
// Não renderize o app até sabermos se o usuário está logado
59+
if (isLoading) {
60+
return <div>Carregando...</div>; // Ou um componente de Spinner
61+
}
62+
63+
return (
64+
<AuthContext.Provider value={{
65+
currentUser,
66+
login,
67+
logout,
68+
isAuthenticated: !!currentUser
69+
}}>
70+
{children}
71+
</AuthContext.Provider>
72+
);
73+
};
74+
75+
// Hook customizado
76+
export const useAuth = () => {
77+
const context = useContext(AuthContext);
78+
if (context === undefined) {
79+
throw new Error('useAuth deve ser usado dentro de um AuthProvider');
80+
}
81+
return context;
82+
};

frontend/src/API/Comment.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface CommentProps {
2+
name: string;
3+
description: string;
4+
technologies: string[];
5+
status: string;
6+
date: string;
7+
}

frontend/src/API/Community.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export interface CommunityProps {
2+
communityID: string;
3+
name: string;
4+
description: string;
5+
keywords: string[];
6+
createdAt: Date;
7+
updatedAt: Date;
8+
}
9+
10+
export async function NewCommunity(data: CommunityProps) {
11+
/* const response = await fetch('http://localhost:3000/api/user/newcommunity', {
12+
method: 'POST',
13+
headers: {
14+
'Content-Type': 'application/json',
15+
'Authorization': `Bearer ${localStorage.getItem('token')}`,
16+
},
17+
body: JSON.stringify(data),
18+
});
19+
20+
if (!response.ok) {
21+
const errorData = await response.json();
22+
throw new Error(errorData.message);
23+
} */
24+
}

0 commit comments

Comments
 (0)