🚀 Auto-deploy: GPI atualizado em 10/08/2026 00:57:32
This commit is contained in:
-55
@@ -1,55 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
import cors from 'cors';
|
|
||||||
import projectRoutes from '../src/server/routes/projectRoutes.js';
|
|
||||||
import partRoutes from '../src/server/routes/partRoutes.js';
|
|
||||||
import paintingSchemeRoutes from '../src/server/routes/paintingSchemeRoutes.js';
|
|
||||||
import applicationRecordRoutes from '../src/server/routes/applicationRecordRoutes.js';
|
|
||||||
import inspectionRoutes from '../src/server/routes/inspectionRoutes.js';
|
|
||||||
import analysisRoutes from '../src/server/routes/analysisRoutes.js';
|
|
||||||
import dataSheetRoutes from '../src/server/routes/dataSheetRoutes.js';
|
|
||||||
import yieldStudyRoutes from '../src/server/routes/yieldStudyRoutes.js';
|
|
||||||
import userRoutes from '../src/server/routes/userRoutes.js';
|
|
||||||
import systemSettingsRoutes from '../src/server/routes/systemSettingsRoutes.js';
|
|
||||||
import geometryTypeRoutes from '../src/server/routes/geometryTypeRoutes.js';
|
|
||||||
import stockRoutes from '../src/server/routes/stockRoutes.js';
|
|
||||||
import notificationRoutes from '../src/server/routes/notificationRoutes.js';
|
|
||||||
import instrumentRoutes from '../src/server/routes/instrumentRoutes.js';
|
|
||||||
import { extractUser } from '../src/server/middleware/authMiddleware.js';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(cors({
|
|
||||||
origin: '*', // Be more specific in production
|
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id', 'x-organization-name']
|
|
||||||
}));
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
// Global Middleware
|
|
||||||
app.use(extractUser);
|
|
||||||
|
|
||||||
// Static Uploads
|
|
||||||
app.use('/uploads', express.static(path.join(process.cwd(), 'uploads')));
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
app.use('/api/users', userRoutes);
|
|
||||||
app.use('/api/projects', projectRoutes);
|
|
||||||
app.use('/api/parts', partRoutes);
|
|
||||||
app.use('/api/painting-schemes', paintingSchemeRoutes);
|
|
||||||
app.use('/api/application-records', applicationRecordRoutes);
|
|
||||||
app.use('/api/inspections', inspectionRoutes);
|
|
||||||
app.use('/api', analysisRoutes);
|
|
||||||
app.use('/api/datasheets', dataSheetRoutes);
|
|
||||||
app.use('/api/yield-studies', yieldStudyRoutes);
|
|
||||||
app.use('/api/system-settings', systemSettingsRoutes);
|
|
||||||
app.use('/api/geometry-types', geometryTypeRoutes);
|
|
||||||
app.use('/api/stock', stockRoutes);
|
|
||||||
app.use('/api/notifications', notificationRoutes);
|
|
||||||
app.use('/api/instruments', instrumentRoutes);
|
|
||||||
|
|
||||||
app.get('/health', (req, res) => {
|
|
||||||
res.json({ status: 'ok', timestamp: new Date() });
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import dotenv from 'dotenv';
|
|||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||||
import app from './app.js';
|
import app from '../src/server/app.js';
|
||||||
import { connectDB } from '../src/server/config/database.js';
|
import { connectDB } from '../src/server/config/database.js';
|
||||||
|
|
||||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# GPI — Gestor de Pintura Industrial
|
||||||
|
## Descritivo Publicitário para Landing Page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Título de Alto Impacto
|
||||||
|
|
||||||
|
**Controle total da pintura industrial: de projeto a inspeção final, em um único sistema.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3 Benefícios Principais
|
||||||
|
|
||||||
|
### 1. Gestão Completa do Projeto de Pintura
|
||||||
|
Cadastre projetos com classificação ambiental ISO 12944 (C1 a C5, CX), controle de peso por evolução, e acompanhamento em tempo real. Cada projeto integra peças, esquemas de pintura, controle de aplicação e inspeções — tudo conectado e auditável.
|
||||||
|
|
||||||
|
### 2. Ferramentas Técnicas Integradas
|
||||||
|
Calculadora de ponto de orvalho, conversor μm/mils, cálculos de EPS/EPU, volume morto, consumo por área e por peso, e seletor de bicos airless — todas as ferramentas que o pintor e o engenheiro precisam, sem sair do sistema.
|
||||||
|
|
||||||
|
### 3. Análise de Conformidade e Rastreabilidade Total
|
||||||
|
Comparativo automático entre valores teóricos e reais (rendimento, diluição, espessura DFT). Relatório por peça/lote com status: Aprovado, Atenção ou Crítico. Inspections registram EPS, aderência, rugosidade e condições ambientais — com证据 visual.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Análise Resumida de Todos os Recursos
|
||||||
|
|
||||||
|
### Gestão de Projetos
|
||||||
|
|
||||||
|
- Cadastro de projetos com cliente, ingeniero responsável, datas e ambiente (ISO 12944)
|
||||||
|
- Acompanhamento de evolução por peso (kg pintados vs total)
|
||||||
|
- Arquivo/restore de projetos
|
||||||
|
- Relatórios gerais e analíticos por projeto
|
||||||
|
|
||||||
|
### Cadastro de Peças e Geometrias
|
||||||
|
|
||||||
|
- Peças com tipo de geometria (viga, chapas, tubulares, etc.)
|
||||||
|
- Perda de eficiência por tipo de geometria (tabela configurável)
|
||||||
|
- Cálculo automático de área superficial e peso
|
||||||
|
- Vinculação a esquemas de pintura
|
||||||
|
|
||||||
|
### Esquemas de Pintura
|
||||||
|
|
||||||
|
- Cadastro de esquemas com camadas (Primer, Intermediate, Finish)
|
||||||
|
- EPS mínima e máxima requerida (μm)
|
||||||
|
- Sólidos por volume (%)
|
||||||
|
- Cor e código de cor com bolinha visual
|
||||||
|
- Diluente/reductor vinculado
|
||||||
|
- Clone de esquemas entre projetos
|
||||||
|
- Troca/importação de esquemas entre projetos
|
||||||
|
|
||||||
|
### Controle de Aplicação (Registros Diários)
|
||||||
|
|
||||||
|
- Registro de demãos por data, etapa e peça
|
||||||
|
- Espessura úmida aplicada (EPU)
|
||||||
|
- Espessura seca calculada (EPS)
|
||||||
|
- Operador, método de aplicação, diluente utilizado
|
||||||
|
- Volume de tinta e diluente consumidos
|
||||||
|
|
||||||
|
### Inspeções de Qualidade
|
||||||
|
|
||||||
|
- **Tratamento Superficial**: Tipo de jateamento (seco, hidrojato, mecânico, manual), grau de limpeza (Sa 1 a Sa 3, St2/St3, WJ-1 a WJ-4), flash rust, 5 leituras de rugosidade com média automática
|
||||||
|
- **Pintura**: Mínimo 10 pontos de EPS, teste de aderência, aparência (aprovado/reprovado/ressalvas), lote de tinta utilizado
|
||||||
|
- **Condições Ambientais**: Temperatura, umidade relativa, temperatura da peça, período (manhã/tarde/noite)
|
||||||
|
- Evolução por peso com gráfico visual
|
||||||
|
|
||||||
|
### Biblioteca Técnica de Produtos (Fichas)
|
||||||
|
|
||||||
|
- Upload de PDF com extração automática de dados via OCR (pdf-parse)
|
||||||
|
- Cadastro de nome, fabricante, tipo de tinta
|
||||||
|
- Sólidos por volume, rendimento teórico, fator de rendimento (m².μm/L)
|
||||||
|
- Intervalos WFT e DFT
|
||||||
|
- Diluição recomendada
|
||||||
|
- Relação de mistura (peso e volume)
|
||||||
|
- Download do PDF técnico
|
||||||
|
|
||||||
|
### Estudos de Rendimento (Yield Studies)
|
||||||
|
|
||||||
|
- Criação de estudos por tinta (ficha técnica)
|
||||||
|
- Categorias de peças (Estrutura Primária, Secundária, etc.)
|
||||||
|
- Dual calculation: por peso (L/t) e por área (m²)
|
||||||
|
- DFT alvo configurável
|
||||||
|
- EPU calculado automaticamente
|
||||||
|
- Projeção de consumo com variação DFT (±10%, ±20%, ±30%)
|
||||||
|
- Gráfico de barras por categoria
|
||||||
|
- Relatório PDF para impressão
|
||||||
|
|
||||||
|
### Estoque de Tintas e Diluentes
|
||||||
|
|
||||||
|
- Cadastro de lotes com RR, número de lote, quantidade, unidade
|
||||||
|
- Validade/expiration date com alerta
|
||||||
|
- Estoque mínimo com alerta visual de baixa quantidade
|
||||||
|
- Baixa de estoque por registro de aplicação
|
||||||
|
- Histórico completo de movimentações por lote
|
||||||
|
- Relatório de inventário para impressão
|
||||||
|
-分开 tintas (PAINT) e diluentes (THINNER)
|
||||||
|
|
||||||
|
### Análise de Conformidade
|
||||||
|
|
||||||
|
- Comparativo teórico vs real por peça/lote
|
||||||
|
- Rendimento (m²/L): real vs meta
|
||||||
|
- Diluição (%): real vs máximo permitido
|
||||||
|
- Espessura DFT: real vs range do esquema
|
||||||
|
- Status global: Aprovado / Atenção / Crítico
|
||||||
|
- Baseado na NBR 12103
|
||||||
|
|
||||||
|
### Calculadora Técnica
|
||||||
|
|
||||||
|
- **Conversões**: Microns (μm) ↔ Mils
|
||||||
|
- **Espessura**: Cálculo de EPS (seca) a partir de EPU, SV e diluição; Cálculo de EPU (úmida) a partir de EPS, SV e diluição; Volume morto (rugosidade + área)
|
||||||
|
- **Ambiente**: Ponto de orvalho com status (risco/seguro)
|
||||||
|
- **Consumo e Custo**: Por área (m²), por peso (L/t), estimativa de custos com preço por litro
|
||||||
|
- **Bicos Airless**: Código -> Ângulo do leque, vazão, descrição do uso (fino/médio/pesado)
|
||||||
|
|
||||||
|
### Ferramentas de Administração
|
||||||
|
|
||||||
|
- Gerenciamento de usuários (admin, user, guest)
|
||||||
|
- Configuração de tipos de geometria com perda de eficiência
|
||||||
|
- Backup e restore do banco de dados
|
||||||
|
- Diagnóstico de sistema (developer dashboard)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diferenciais do Mercado Concorrente
|
||||||
|
|
||||||
|
| Concorrentes Tradicionais | GPI |
|
||||||
|
|---------------------------|-----|
|
||||||
|
| Planilhas Excel isoladas | **Sistema centralizado** com projetos, peças, esquemas e inspeções integrados |
|
||||||
|
| Cálculos manuais ou calculadora separada | **Ferramentas técnicas integradas** — ponto de orvalho, EPS/EPU, bicos airless |
|
||||||
|
| Fichas técnicas em papel ou arquivo | **Biblioteca digital com OCR** — extração automática de PDF |
|
||||||
|
| Análise de conformidade em planilha | **Comparativo automático** teórico vs real com status visual |
|
||||||
|
| Estoque em planilha ou ERP genérico | **Gestão específica de tintas** com lotes, validade e alertas |
|
||||||
|
| Estudos de rendimento em papel | **Dual calculation** peso/área com projeção DFT e gráfico |
|
||||||
|
| Relatórios fragmentados | **Relatórios PDF** de inspeção, inventário e estudos |
|
||||||
|
| Sem norma técnica | **Conformidade NBR 12103** para análise de rendimento |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inovações Tecnológicas
|
||||||
|
|
||||||
|
1. **PDF Data Extraction via OCR**: Upload de fichas técnicas em PDF com extração automática de sólidos volume, rendimento, DFT — usando pdf-parse e processamento inteligente.
|
||||||
|
|
||||||
|
2. **Dual Yield Calculation**: Cálculo de consumo de tinta tanto por peso (L/t) quanto por área (m²) — com fator de eficiência dinâmica por tipo de geometria.
|
||||||
|
|
||||||
|
3. **Calculadora de Bicos Airless**: Decodificação de códigos de bicos (ex: 517) em ângulo de leque, vazão e recomendação de uso — ferramenta essencial para pintores.
|
||||||
|
|
||||||
|
4. **Análise de Conformidade Automática**: Comparativo em tempo real entre valores teóricos (esquema) e reais (aplicação/inspeção) — com thresholds de 20% (atenção) e 30% (crítico).
|
||||||
|
|
||||||
|
5. **Sistema de Estocagem com Lotes e Validade**: Gestão de estoque específica para pintura industrial com追踪 de lotes, datas de validade e alertas de estoque mínimo.
|
||||||
|
|
||||||
|
6. **Surface Treatment com 5 Leituras de Rugosidade**: Formulário completo de jateamento com avg/min/max de rugosidade, flash rust e condições ambientais — tudo integrado à inspeção.
|
||||||
|
|
||||||
|
7. **Yield Study com Projeção DFT**: Estudos de rendimento com projeção de consumo para variações de espessura (±10%, ±20%, ±30%) e gráfico visual por categoria.
|
||||||
|
|
||||||
|
8. **Inspection com Evolução Visual**: Gráfico de pizza mostrando evolução do projeto por peso da inspeção vs peso total — integrado ao relatório de inspeção.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefícios Incontestáveis
|
||||||
|
|
||||||
|
- **Fim da dispersão**: Tudo — projeto, peça, esquema, controle, inspeção — em um único sistema. Sem planilhas espalhadas, sem planilhas perdidas.
|
||||||
|
- **Decisões baseadas em dados**: Análise de conformidade automática elimina o "achismo" — o sistema diz se está aprovado, em atenção ou crítico.
|
||||||
|
- **Precisão nos cálculos**: EPS, EPU, volume morto, ponto de orvalho — todas as fórmulas da norma NBR 12103 implementadas e validadas.
|
||||||
|
- **Rastreabilidade de lotes**: Cada inspeção registra qual lote de tinta foi usado — informação crítica para recalls e certificações de qualidade.
|
||||||
|
- **Eliminação de erros de transcrição**: Upload de PDF com extração automática dispensa digitação manual de dados técnicos de fichas.
|
||||||
|
- **Produtividade no canteiro**: Ferramentas técnicas integradas (calculadora, bicos, conversão) eliminam idas e vindas ao escritório para cálculos.
|
||||||
|
- **Entregas auditáveis**: Relatórios PDF de inspeção e estudos de rendimento servem como documento de entrega ao cliente — com evidência visual.
|
||||||
|
- **Configuração por tipo de geometria**: Cada tipo de peça tem sua própria perda de eficiência — estruturas diferentes, cálculos diferentes, sem simplificações.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parágrafo Final — A Dor da Gestão
|
||||||
|
|
||||||
|
Gerenciar pintura industrial é lidar com planilhas desatualizadas, cálculos manuais prone a erros, fichas técnicas em papel espalhadas pelo canteiro, e relatórios fragmentados que não se talking between si. O gestor precisa saber: quanto de tinta foi consumido, se a espessura está dentro da faixa, se há lotes prestes a vencer, e se o rendimento real está dentro do esperado. O **GPI** centraliza projetos, peças, esquemas de pintura, controle de aplicação, inspeções e estoque — com ferramentas técnicas integradas e análise de conformidade automática. Menos planilhas, mais controle, entregas dentro da norma. **Seu próximo projeto de pintura merece um sistema que trabalha tão hard quanto você — do primeiro projeto ao relatório final.**
|
||||||
@@ -2,7 +2,6 @@ console.log('Loading Netlify Function...');
|
|||||||
import serverless from 'serverless-http';
|
import serverless from 'serverless-http';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import mongoose from 'mongoose';
|
|
||||||
|
|
||||||
// Import local database connection that sets up GridFS/Bucket
|
// Import local database connection that sets up GridFS/Bucket
|
||||||
import { connectDB } from '../../src/server/config/database.js';
|
import { connectDB } from '../../src/server/config/database.js';
|
||||||
@@ -50,8 +49,7 @@ app.use('/uploads', uploadsRoutes);
|
|||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
timestamp: new Date(),
|
timestamp: new Date()
|
||||||
mongoState: mongoose.connection.readyState
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Generated
+1191
-593
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,8 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"enhanced-resolve": "^5.18.4",
|
"enhanced-resolve": "^5.18.4",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
"express-rate-limit": "^8.6.2",
|
||||||
|
"helmet": "^8.3.0",
|
||||||
"jose": "^5.2.0",
|
"jose": "^5.2.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"mongodb": "^7.1.1",
|
"mongodb": "^7.1.1",
|
||||||
|
|||||||
@@ -2,26 +2,18 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|||||||
import type { AppUser } from '../types';
|
import type { AppUser } from '../types';
|
||||||
import { AuthContext } from './AuthContextType';
|
import { AuthContext } from './AuthContextType';
|
||||||
import { setApiOrganizationId } from '../services/api';
|
import { setApiOrganizationId } from '../services/api';
|
||||||
|
import api from '../services/api';
|
||||||
|
|
||||||
interface AuthProviderProps {
|
interface AuthProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultUser: AppUser = {
|
|
||||||
id: '00000000-0000-0000-0000-000000000000',
|
|
||||||
email: 'guest@gpi.app',
|
|
||||||
name: 'Guest User',
|
|
||||||
role: 'user',
|
|
||||||
isBanned: false,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
updatedAt: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_ORGANIZATION_ID = 'e47e6210-4879-4e5b-bf21-9285d2713123';
|
const DEFAULT_ORGANIZATION_ID = 'e47e6210-4879-4e5b-bf21-9285d2713123';
|
||||||
const DEFAULT_ORGANIZATION_NAME = 'Organização Padrão';
|
const DEFAULT_ORGANIZATION_NAME = 'Organização Padrão';
|
||||||
|
|
||||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||||
const [appUser, setAppUser] = useState<AppUser | null>(null);
|
const [appUser, setAppUser] = useState<AppUser | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('gpi_user');
|
const storedUser = localStorage.getItem('gpi_user');
|
||||||
@@ -34,25 +26,26 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setApiOrganizationId(DEFAULT_ORGANIZATION_ID, DEFAULT_ORGANIZATION_NAME);
|
setApiOrganizationId(DEFAULT_ORGANIZATION_ID, DEFAULT_ORGANIZATION_NAME);
|
||||||
|
setIsLoading(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const signInWithPassword = async (password: string): Promise<boolean> => {
|
const signInWithPassword = async (password: string): Promise<boolean> => {
|
||||||
if (password === '@@Gi05Br;;') {
|
try {
|
||||||
const adminUser: AppUser = {
|
const response = await api.post('/users/login', { password });
|
||||||
...defaultUser,
|
if (response.data.success) {
|
||||||
id: 'admin-001',
|
const adminUser = response.data.user as AppUser;
|
||||||
email: 'admtracksteel@gmail.com',
|
|
||||||
name: 'Administrator / DEV',
|
|
||||||
role: 'admin'
|
|
||||||
};
|
|
||||||
setAppUser(adminUser);
|
setAppUser(adminUser);
|
||||||
localStorage.setItem('gpi_user', JSON.stringify(adminUser));
|
localStorage.setItem('gpi_user', JSON.stringify(adminUser));
|
||||||
|
localStorage.setItem('gpi_token', response.data.token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Login failed:", error);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDeveloper = useCallback(() => appUser?.email === 'admtracksteel@gmail.com', [appUser]);
|
const isDeveloper = useCallback(() => appUser?.email === 'admtracksteel@gmail.com' || appUser?.id === 'admin-user', [appUser]);
|
||||||
const isAdmin = useCallback(() => appUser?.role === 'admin' || appUser?.email === 'admtracksteel@gmail.com', [appUser]);
|
const isAdmin = useCallback(() => appUser?.role === 'admin' || appUser?.email === 'admtracksteel@gmail.com', [appUser]);
|
||||||
const isUser = useCallback(() => !!appUser, [appUser]);
|
const isUser = useCallback(() => !!appUser, [appUser]);
|
||||||
const isGuest = useCallback(() => !appUser, [appUser]);
|
const isGuest = useCallback(() => !appUser, [appUser]);
|
||||||
@@ -61,7 +54,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const value = useMemo(() => ({
|
const value = useMemo(() => ({
|
||||||
appUser,
|
appUser,
|
||||||
isLoading: false,
|
isLoading,
|
||||||
isSignedIn: !!appUser,
|
isSignedIn: !!appUser,
|
||||||
error: null,
|
error: null,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
@@ -71,7 +64,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
canEdit,
|
canEdit,
|
||||||
refetchUser,
|
refetchUser,
|
||||||
signInWithPassword
|
signInWithPassword
|
||||||
}), [appUser, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser, signInWithPassword]);
|
}), [appUser, isLoading, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={value}>
|
||||||
|
|||||||
+5
-8
@@ -3,20 +3,17 @@ import './index.css'
|
|||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
return 'guest-token';
|
return localStorage.getItem('gpi_token');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUser() {
|
export function getUser() {
|
||||||
return {
|
const user = localStorage.getItem('gpi_user');
|
||||||
id: 'guest-user',
|
return user ? JSON.parse(user) : null;
|
||||||
email: 'guest@gpi.app',
|
|
||||||
name: 'Guest User',
|
|
||||||
role: 'user'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUser(token: string, user: any) {
|
export function setUser(token: string, user: any) {
|
||||||
console.log('User set (no auth):', user);
|
localStorage.setItem('gpi_token', token);
|
||||||
|
localStorage.setItem('gpi_user', JSON.stringify(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(<App />)
|
createRoot(document.getElementById('root')!).render(<App />)
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// API service configuration v2.0 - Logto Auth
|
// API service configuration v2.0 - Logto Auth
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { triggerGuestWarning } from '../utils/toastHandler';
|
import { triggerGuestWarning } from '../utils/toastHandler';
|
||||||
import { getToken } from '../main';
|
|
||||||
|
|
||||||
export const getBaseUrl = () => {
|
export const getBaseUrl = () => {
|
||||||
if (import.meta.env.VITE_API_URL) {
|
if (import.meta.env.VITE_API_URL) {
|
||||||
@@ -29,7 +28,7 @@ export const setApiOrganizationId = setApiOrgData;
|
|||||||
|
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = getToken();
|
const token = localStorage.getItem('gpi_token');
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Authorization'] = `Bearer ${token}`;
|
config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-32
@@ -1,5 +1,12 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
import rateLimit from 'express-rate-limit';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
import { extractUser } from './middleware/authMiddleware.js';
|
||||||
|
|
||||||
import projectRoutes from './routes/projectRoutes.js';
|
import projectRoutes from './routes/projectRoutes.js';
|
||||||
import partRoutes from './routes/partRoutes.js';
|
import partRoutes from './routes/partRoutes.js';
|
||||||
import paintingSchemeRoutes from './routes/paintingSchemeRoutes.js';
|
import paintingSchemeRoutes from './routes/paintingSchemeRoutes.js';
|
||||||
@@ -11,76 +18,104 @@ import yieldStudyRoutes from './routes/yieldStudyRoutes.js';
|
|||||||
import userRoutes from './routes/userRoutes.js';
|
import userRoutes from './routes/userRoutes.js';
|
||||||
import systemSettingsRoutes from './routes/systemSettingsRoutes.js';
|
import systemSettingsRoutes from './routes/systemSettingsRoutes.js';
|
||||||
import geometryTypeRoutes from './routes/geometryTypeRoutes.js';
|
import geometryTypeRoutes from './routes/geometryTypeRoutes.js';
|
||||||
|
|
||||||
import stockRoutes from './routes/stockRoutes.js';
|
import stockRoutes from './routes/stockRoutes.js';
|
||||||
import notificationRoutes from './routes/notificationRoutes.js';
|
import notificationRoutes from './routes/notificationRoutes.js';
|
||||||
import instrumentRoutes from './routes/instrumentRoutes.js';
|
import instrumentRoutes from './routes/instrumentRoutes.js';
|
||||||
import messageRoutes from './routes/messageRoutes.js';
|
import messageRoutes from './routes/messageRoutes.js';
|
||||||
import backupRoutes from './routes/backupRoutes.js';
|
import backupRoutes from './routes/backupRoutes.js';
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
// Security Headers
|
||||||
|
app.use(helmet({
|
||||||
|
contentSecurityPolicy: false, // Pode interferir com o React em dev se não configurado direito
|
||||||
|
crossOriginEmbedderPolicy: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Rate Limiter
|
||||||
|
const apiLimiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 1000, // limit each IP to 1000 requests per windowMs
|
||||||
|
message: 'Too many requests from this IP, please try again later.'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply rate limiter only to API routes
|
||||||
|
app.use('/api', apiLimiter);
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
const allowedOrigins = [
|
||||||
|
'https://gpi.reifonas.cloud',
|
||||||
|
'http://localhost:5173',
|
||||||
|
'http://localhost:3000'
|
||||||
|
];
|
||||||
|
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: '*',
|
origin: (origin, callback) => {
|
||||||
|
if (!origin || allowedOrigins.includes(origin)) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Not allowed by CORS'));
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id']
|
allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id']
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.use(express.json({ limit: '50mb' }));
|
app.use(express.json({ limit: '5mb' }));
|
||||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
app.use(express.urlencoded({ limit: '5mb', extended: true }));
|
||||||
import { extractUser } from './middleware/authMiddleware.js';
|
|
||||||
app.use(extractUser);
|
|
||||||
|
|
||||||
// Static Uploads
|
// Static Uploads
|
||||||
import fs from 'fs';
|
|
||||||
const uploadsPath = path.join(process.cwd(), 'uploads');
|
const uploadsPath = path.join(process.cwd(), 'uploads');
|
||||||
|
|
||||||
if (!fs.existsSync(uploadsPath)) {
|
if (!fs.existsSync(uploadsPath)) {
|
||||||
fs.mkdirSync(uploadsPath, { recursive: true });
|
fs.mkdirSync(uploadsPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use('/uploads', express.static(uploadsPath));
|
app.use('/uploads', express.static(uploadsPath));
|
||||||
|
|
||||||
// Serve frontend static files
|
// Serve frontend static files
|
||||||
const distPath = path.join(process.cwd(), 'dist');
|
const distPath = path.join(process.cwd(), 'dist');
|
||||||
app.use(express.static(distPath));
|
app.use(express.static(distPath));
|
||||||
|
|
||||||
// Routes
|
// API Routes (all API routes pass through extractUser here, avoid duplication in route files)
|
||||||
app.use('/api/users', userRoutes);
|
const apiRouter = express.Router();
|
||||||
app.use('/api/projects', projectRoutes);
|
apiRouter.use(extractUser);
|
||||||
app.use('/api/parts', partRoutes);
|
|
||||||
app.use('/api/painting-schemes', paintingSchemeRoutes);
|
apiRouter.use('/users', userRoutes);
|
||||||
app.use('/api/application-records', applicationRecordRoutes);
|
apiRouter.use('/projects', projectRoutes);
|
||||||
app.use('/api/inspections', inspectionRoutes);
|
apiRouter.use('/parts', partRoutes);
|
||||||
app.use('/api', analysisRoutes);
|
apiRouter.use('/painting-schemes', paintingSchemeRoutes);
|
||||||
app.use('/api/datasheets', dataSheetRoutes);
|
apiRouter.use('/application-records', applicationRecordRoutes);
|
||||||
app.use('/api/datasheets', dataSheetRoutes);
|
apiRouter.use('/inspections', inspectionRoutes);
|
||||||
app.use('/api/yield-studies', yieldStudyRoutes);
|
apiRouter.use('/', analysisRoutes);
|
||||||
app.use('/api/system-settings', systemSettingsRoutes);
|
apiRouter.use('/datasheets', dataSheetRoutes);
|
||||||
app.use('/api/geometry-types', geometryTypeRoutes);
|
apiRouter.use('/yield-studies', yieldStudyRoutes);
|
||||||
app.use('/api/stock', stockRoutes);
|
apiRouter.use('/system-settings', systemSettingsRoutes);
|
||||||
app.use('/api/notifications', notificationRoutes);
|
apiRouter.use('/geometry-types', geometryTypeRoutes);
|
||||||
app.use('/api/instruments', instrumentRoutes);
|
apiRouter.use('/stock', stockRoutes);
|
||||||
app.use('/api/messages', messageRoutes);
|
apiRouter.use('/notifications', notificationRoutes);
|
||||||
app.use('/api/backup', backupRoutes);
|
apiRouter.use('/instruments', instrumentRoutes);
|
||||||
|
apiRouter.use('/messages', messageRoutes);
|
||||||
|
apiRouter.use('/backup', backupRoutes);
|
||||||
|
|
||||||
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date(), auth: 'logto' });
|
res.json({ status: 'ok', timestamp: new Date(), auth: 'logto' });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/test', (req, res) => {
|
|
||||||
res.json({ status: 'ok', message: 'Test endpoint working' });
|
|
||||||
});
|
|
||||||
|
|
||||||
// SPA fallback - must be last
|
// SPA fallback - must be last
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
if (req.path.startsWith('/api')) {
|
||||||
|
return next(); // Do not return index.html for API 404s
|
||||||
|
}
|
||||||
res.sendFile(path.join(distPath, 'index.html'));
|
res.sendFile(path.join(distPath, 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
console.error('Express Error:', err);
|
console.error('Express Error:', err);
|
||||||
|
if (err.message === 'Not allowed by CORS') {
|
||||||
|
return res.status(403).json({ error: 'CORS error' });
|
||||||
|
}
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { createClient } from '@supabase/supabase-js';
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const supabaseUrl = process.env.SUPABASE_URL || 'https://supabase.reifonas.cloud';
|
const supabaseUrl = process.env.SUPABASE_URL;
|
||||||
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||||
|
|
||||||
if (!supabaseServiceKey) {
|
if (!supabaseUrl || !supabaseServiceKey) {
|
||||||
throw new Error('❌ SUPABASE_SERVICE_ROLE_KEY is missing in environment variables');
|
throw new Error('❌ SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY must be provided in environment variables');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GPI_SCHEMA = 'public';
|
export const GPI_SCHEMA = 'public';
|
||||||
@@ -68,5 +68,3 @@ export async function findOneGpi(table: string, filters: Record<string, any>) {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Supabase client initialized');
|
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { supabase } from '../config/supabase.js';
|
import { supabase } from '../config/supabase.js';
|
||||||
|
|
||||||
const TABLE_NOT_FOUND_CODES = ['42P01', 'PGRST116'];
|
|
||||||
|
|
||||||
const safeSupabaseQuery = async (table: string, query: any) => {
|
|
||||||
try {
|
|
||||||
return await query;
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.code && TABLE_NOT_FOUND_CODES.includes(error.code)) {
|
|
||||||
console.log(`Table ${table} not found, returning empty result`);
|
|
||||||
return { data: [], error: null };
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
export const sendMessage = async (req: Request, res: Response) => {
|
export const sendMessage = async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -192,3 +192,30 @@ export const deleteUser = async (req: Request, res: Response) => {
|
|||||||
res.json({ message: 'Membro removido com sucesso.' });
|
res.json({ message: 'Membro removido com sucesso.' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const login = async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { password } = req.body;
|
||||||
|
// In a real app, this should check against a hashed DB value or Logto.
|
||||||
|
// For now, moving the hardcoded password to the backend is safer than frontend.
|
||||||
|
const adminPassword = process.env.ADMIN_PASSWORD || '@@Gi05Br;;';
|
||||||
|
|
||||||
|
if (password === adminPassword) {
|
||||||
|
// Success - return a token
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
token: 'gpi-admin-token',
|
||||||
|
user: {
|
||||||
|
id: 'admin-user',
|
||||||
|
role: 'admin',
|
||||||
|
name: 'Administrador'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(401).json({ success: false, error: 'Senha incorreta.' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
res.status(500).json({ success: false, error: 'Erro interno.' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -155,5 +155,3 @@ export const YieldStudy = createModel('yield_studies');
|
|||||||
export const StoredFile = createModel('stored_files');
|
export const StoredFile = createModel('stored_files');
|
||||||
|
|
||||||
export { queryGpi, findOneGpi, insertGpi, updateGpi, deleteGpi };
|
export { queryGpi, findOneGpi, insertGpi, updateGpi, deleteGpi };
|
||||||
|
|
||||||
console.log('✅ Mongoose Compatibility Layer load complete (Extended Mode)');
|
|
||||||
|
|||||||
+10
-4
@@ -36,9 +36,17 @@ export async function getModel(tableName: string) {
|
|||||||
return existing;
|
return existing;
|
||||||
},
|
},
|
||||||
countDocuments: async (query: any = {}) => {
|
countDocuments: async (query: any = {}) => {
|
||||||
const { data, error } = await supabase.from(tableName).select('*', { count: 'exact', head: true });
|
let q = supabase.from(tableName).select('*', { count: 'exact', head: true });
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
Object.keys(query).forEach(key => {
|
||||||
|
q = q.eq(key, query[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { count, error } = await q;
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return data?.length || 0;
|
return count || 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -67,5 +75,3 @@ export function getModelById(tableName: string, idField: string = 'id') {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ DB Compatibility Layer initialized');
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { authenticateRequest } from './logtoAuth.js';
|
||||||
|
|
||||||
export interface IAppUser {
|
export interface IAppUser {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,22 +17,47 @@ declare module 'express-serve-static-core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractUser = async (req: Request, res: Response, next: NextFunction) => {
|
const DEFAULT_ORG_ID = 'e47e6210-4879-4e5b-bf21-9285d2713123';
|
||||||
req.appUser = {
|
|
||||||
|
const GUEST_USER: IAppUser = {
|
||||||
id: '00000000-0000-0000-0000-000000000000',
|
id: '00000000-0000-0000-0000-000000000000',
|
||||||
logtoId: 'guest',
|
logtoId: 'guest',
|
||||||
email: 'guest@gpi.app',
|
email: 'guest@gpi.app',
|
||||||
name: 'Guest User',
|
name: 'Guest User',
|
||||||
role: 'user',
|
role: 'guest',
|
||||||
organizationId: req.headers['x-organization-id'] as string || 'e47e6210-4879-4e5b-bf21-9285d2713123',
|
organizationId: DEFAULT_ORG_ID,
|
||||||
organizationRole: 'user'
|
organizationRole: 'guest'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const authenticatedUser = await authenticateRequest(req);
|
||||||
|
if (authenticatedUser) {
|
||||||
|
req.appUser = {
|
||||||
|
...authenticatedUser,
|
||||||
|
organizationId: req.headers['x-organization-id'] as string || DEFAULT_ORG_ID,
|
||||||
|
organizationRole: authenticatedUser.role
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
req.appUser = {
|
||||||
|
...GUEST_USER,
|
||||||
|
organizationId: req.headers['x-organization-id'] as string || DEFAULT_ORG_ID,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
req.appUser = {
|
||||||
|
...GUEST_USER,
|
||||||
|
organizationId: req.headers['x-organization-id'] as string || DEFAULT_ORG_ID,
|
||||||
|
};
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireRole = (allowedRoles: string[]) => {
|
export const requireRole = (allowedRoles: string[]) => {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
// No authentication required - allow all requests
|
if (!req.appUser || !allowedRoles.includes(req.appUser.role)) {
|
||||||
|
return res.status(403).json({ error: 'Acesso negado. Permissão insuficiente.' });
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -40,9 +66,15 @@ export const requireAdmin = requireRole(['admin']);
|
|||||||
export const requireUser = requireRole(['user', 'admin']);
|
export const requireUser = requireRole(['user', 'admin']);
|
||||||
|
|
||||||
export const canEdit = (req: Request, res: Response, next: NextFunction) => {
|
export const canEdit = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (!req.appUser || req.appUser.role === 'guest') {
|
||||||
|
return res.status(403).json({ error: 'Convidados não podem editar. Faça login para ter acesso completo.' });
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireDeveloper = (req: Request, res: Response, next: NextFunction) => {
|
export const requireDeveloper = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (!req.appUser || req.appUser.email !== 'admtracksteel@gmail.com') {
|
||||||
|
return res.status(403).json({ error: 'Acesso restrito ao desenvolvedor.' });
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as appRecordController from '../controllers/applicationRecordController.js';
|
import * as appRecordController from '../controllers/applicationRecordController.js';
|
||||||
import { extractUser, requireUser } from '../middleware/authMiddleware.js';
|
import { requireUser } from '../middleware/authMiddleware.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Public routes (read-only)
|
// Public routes (read-only)
|
||||||
router.get('/project/:projectId', extractUser, appRecordController.getApplicationRecordsByProject);
|
router.get('/project/:projectId', appRecordController.getApplicationRecordsByProject);
|
||||||
|
|
||||||
// Protected routes (require user permission)
|
// Protected routes (require user permission)
|
||||||
router.post('/', extractUser, requireUser, appRecordController.createApplicationRecord);
|
router.post('/', requireUser, appRecordController.createApplicationRecord);
|
||||||
router.put('/:id', extractUser, requireUser, appRecordController.updateApplicationRecord);
|
router.put('/:id', requireUser, appRecordController.updateApplicationRecord);
|
||||||
router.delete('/:id', extractUser, requireUser, appRecordController.deleteApplicationRecord);
|
router.delete('/:id', requireUser, appRecordController.deleteApplicationRecord);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as inspectionController from '../controllers/inspectionController.js';
|
import * as inspectionController from '../controllers/inspectionController.js';
|
||||||
import { extractUser, requireUser } from '../middleware/authMiddleware.js';
|
import { requireUser } from '../middleware/authMiddleware.js';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@@ -42,15 +42,15 @@ const upload = multer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Upload route
|
// Upload route
|
||||||
router.post('/upload', extractUser, requireUser, upload.single('photo'), inspectionController.uploadPhoto);
|
router.post('/upload', requireUser, upload.single('photo'), inspectionController.uploadPhoto);
|
||||||
|
|
||||||
// Public routes (read-only)
|
// Public routes (read-only)
|
||||||
router.get('/', extractUser, inspectionController.getAllInspections);
|
router.get('/', inspectionController.getAllInspections);
|
||||||
router.get('/project/:projectId', extractUser, inspectionController.getInspectionsByProject);
|
router.get('/project/:projectId', inspectionController.getInspectionsByProject);
|
||||||
|
|
||||||
// Protected routes (require user permission)
|
// Protected routes (require user permission)
|
||||||
router.post('/', extractUser, requireUser, inspectionController.createInspection);
|
router.post('/', requireUser, inspectionController.createInspection);
|
||||||
router.put('/:id', extractUser, requireUser, inspectionController.updateInspection);
|
router.put('/:id', requireUser, inspectionController.updateInspection);
|
||||||
router.delete('/:id', extractUser, requireUser, inspectionController.deleteInspection);
|
router.delete('/:id', requireUser, inspectionController.deleteInspection);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { sendMessage, getUnreadMessages, markMessageAsRead, getMyPendingMessages, deleteMessage, archiveMessage, recipientDeleteMessage } from '../controllers/messageController.js';
|
import { sendMessage, getUnreadMessages, markMessageAsRead, getMyPendingMessages, deleteMessage, archiveMessage, recipientDeleteMessage } from '../controllers/messageController.js';
|
||||||
import { extractUser } from '../middleware/authMiddleware.js';
|
import { } from '../middleware/authMiddleware.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// All routes require authentication
|
// All routes require authentication
|
||||||
router.post('/', extractUser, sendMessage);
|
router.post('/', sendMessage);
|
||||||
router.get('/unread', extractUser, getUnreadMessages);
|
router.get('/unread', getUnreadMessages);
|
||||||
router.get('/pending', extractUser, getMyPendingMessages);
|
router.get('/pending', getMyPendingMessages);
|
||||||
router.patch('/:id/read', extractUser, markMessageAsRead);
|
router.patch('/:id/read', markMessageAsRead);
|
||||||
router.patch('/:id/archive', extractUser, archiveMessage);
|
router.patch('/:id/archive', archiveMessage);
|
||||||
router.delete('/:id', extractUser, deleteMessage); // Sender side (if unread)
|
router.delete('/:id', deleteMessage); // Sender side (if unread)
|
||||||
router.delete('/:id/recipient', extractUser, recipientDeleteMessage); // Recipient side
|
router.delete('/:id/recipient', recipientDeleteMessage); // Recipient side
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as partController from '../controllers/partController.js';
|
import * as partController from '../controllers/partController.js';
|
||||||
import { extractUser, requireAdmin } from '../middleware/authMiddleware.js';
|
import { requireAdmin } from '../middleware/authMiddleware.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Public routes (read-only)
|
// Public routes (read-only)
|
||||||
router.get('/', extractUser, partController.getAllParts);
|
router.get('/', partController.getAllParts);
|
||||||
router.get('/project/:projectId', extractUser, partController.getPartsByProject);
|
router.get('/project/:projectId', partController.getPartsByProject);
|
||||||
|
|
||||||
// Protected routes (require admin permission)
|
// Protected routes (require admin permission)
|
||||||
router.post('/', extractUser, requireAdmin, partController.createPart);
|
router.post('/', requireAdmin, partController.createPart);
|
||||||
router.put('/:id', extractUser, requireAdmin, partController.updatePart);
|
router.put('/:id', requireAdmin, partController.updatePart);
|
||||||
router.delete('/:id', extractUser, requireAdmin, partController.deletePart);
|
router.delete('/:id', requireAdmin, partController.deletePart);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as projectController from '../controllers/projectController.js';
|
import * as projectController from '../controllers/projectController.js';
|
||||||
import { extractUser, requireAdmin } from '../middleware/authMiddleware.js';
|
import { requireAdmin } from '../middleware/authMiddleware.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Rotas com contexto de usuário (leitura)
|
// Rotas com contexto de usuário (leitura)
|
||||||
router.get('/', extractUser, projectController.getAllProjects);
|
router.get('/', projectController.getAllProjects);
|
||||||
router.get('/dashboard', extractUser, projectController.getDashboardProjects);
|
router.get('/dashboard', projectController.getDashboardProjects);
|
||||||
router.get('/:id', extractUser, projectController.getProjectById);
|
router.get('/:id', projectController.getProjectById);
|
||||||
|
|
||||||
// Protected routes (require admin permission)
|
// Protected routes (require admin permission)
|
||||||
router.post('/', extractUser, requireAdmin, projectController.createProject);
|
router.post('/', requireAdmin, projectController.createProject);
|
||||||
router.put('/:id', extractUser, requireAdmin, projectController.updateProject);
|
router.put('/:id', requireAdmin, projectController.updateProject);
|
||||||
router.delete('/:id', extractUser, requireAdmin, projectController.deleteProject);
|
router.delete('/:id', requireAdmin, projectController.deleteProject);
|
||||||
router.patch('/:id/archive', extractUser, requireAdmin, projectController.archiveProject);
|
router.patch('/:id/archive', requireAdmin, projectController.archiveProject);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getSettings, updateSettings, uploadLogo, serveLogo } from '../controllers/systemSettingsController.js';
|
import { getSettings, updateSettings, uploadLogo, serveLogo } from '../controllers/systemSettingsController.js';
|
||||||
import { extractUser, requireDeveloper } from '../middleware/authMiddleware.js';
|
import { requireDeveloper } from '../middleware/authMiddleware.js';
|
||||||
import { uploadLogoDetails } from '../middleware/uploadMiddleware.js';
|
import { uploadLogoDetails } from '../middleware/uploadMiddleware.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -9,10 +9,10 @@ const router = express.Router();
|
|||||||
router.get('/', getSettings);
|
router.get('/', getSettings);
|
||||||
|
|
||||||
// Protected update
|
// Protected update
|
||||||
router.put('/', extractUser, requireDeveloper, updateSettings);
|
router.put('/', requireDeveloper, updateSettings);
|
||||||
|
|
||||||
// Protected upload
|
// Protected upload
|
||||||
router.post('/logo', extractUser, requireDeveloper, uploadLogoDetails.single('logo'), uploadLogo);
|
router.post('/logo', requireDeveloper, uploadLogoDetails.single('logo'), uploadLogo);
|
||||||
|
|
||||||
// Public access to view logo (noauth needed for login screen etc)
|
// Public access to view logo (noauth needed for login screen etc)
|
||||||
router.get('/logo-image/:filename', (req, res) => {
|
router.get('/logo-image/:filename', (req, res) => {
|
||||||
@@ -23,8 +23,8 @@ router.get('/logo-image/:filename', (req, res) => {
|
|||||||
// Global Admin Routes
|
// Global Admin Routes
|
||||||
import { getGlobalUsers, getGlobalOrganizations, toggleOrganizationBan } from '../controllers/systemSettingsController.js';
|
import { getGlobalUsers, getGlobalOrganizations, toggleOrganizationBan } from '../controllers/systemSettingsController.js';
|
||||||
|
|
||||||
router.get('/users', extractUser, requireDeveloper, getGlobalUsers);
|
router.get('/users', requireDeveloper, getGlobalUsers);
|
||||||
router.get('/organizations', extractUser, requireDeveloper, getGlobalOrganizations);
|
router.get('/organizations', requireDeveloper, getGlobalOrganizations);
|
||||||
router.post('/organizations/ban', extractUser, requireDeveloper, toggleOrganizationBan);
|
router.post('/organizations/ban', requireDeveloper, toggleOrganizationBan);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { syncUser, getCurrentUser, getAllUsers, updateUserRole, toggleBanUser, heartbeat, getActiveUsers, deleteUser } from '../controllers/userController.js';
|
import { syncUser, getCurrentUser, getAllUsers, updateUserRole, toggleBanUser, heartbeat, getActiveUsers, deleteUser, login } from '../controllers/userController.js';
|
||||||
import { extractUser } from '../middleware/authMiddleware.js';
|
import { requireAdmin, requireUser } from '../middleware/authMiddleware.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Public routes (no auth required)
|
// Public routes (no auth required originally, but now all API has extractUser)
|
||||||
router.get('/', getAllUsers);
|
// Only users should see all users
|
||||||
router.get('/me', extractUser, getCurrentUser);
|
router.get('/', requireUser, getAllUsers);
|
||||||
|
router.get('/me', getCurrentUser);
|
||||||
|
router.post('/login', login);
|
||||||
|
|
||||||
// Heartbeat & Presence
|
// Heartbeat & Presence
|
||||||
router.post('/heartbeat', extractUser, heartbeat);
|
router.post('/heartbeat', heartbeat);
|
||||||
router.get('/active', extractUser, getActiveUsers);
|
router.get('/active', getActiveUsers);
|
||||||
|
|
||||||
// Admin routes
|
// Admin routes
|
||||||
router.patch('/:id/role', extractUser, updateUserRole);
|
router.patch('/:id/role', requireAdmin, updateUserRole);
|
||||||
router.patch('/:id/ban', extractUser, toggleBanUser);
|
router.patch('/:id/ban', requireAdmin, toggleBanUser);
|
||||||
router.delete('/:id', extractUser, deleteUser);
|
router.delete('/:id', requireAdmin, deleteUser);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -60,17 +60,10 @@ function getContentType(ext: string): string {
|
|||||||
return types[ext.toLowerCase()] || 'application/octet-stream';
|
return types[ext.toLowerCase()] || 'application/octet-stream';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveFileToGridFS = saveFileToStorage;
|
|
||||||
export const deleteFileFromGridFS = deleteFileFromStorage;
|
|
||||||
|
|
||||||
export const getFileStream = (fileUrl: string) => {
|
export const getFileStream = (fileUrl: string) => {
|
||||||
return fileUrl;
|
return fileUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const migrateFilesToGridFS = async () => {
|
|
||||||
console.log('ℹ️ File migration skipped - using Supabase Storage');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAllDataSheets = async (organizationId?: string) => {
|
export const getAllDataSheets = async (organizationId?: string) => {
|
||||||
try {
|
try {
|
||||||
let query = supabase.from('technical_data_sheets').select('*');
|
let query = supabase.from('technical_data_sheets').select('*');
|
||||||
@@ -138,4 +131,3 @@ export const deleteDataSheet = async (id: string, organizationId?: string) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ DataSheetService loaded with Supabase Storage');
|
|
||||||
@@ -93,4 +93,3 @@ export const getInspectionStats = async (organizationId?: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ InspectionService loaded with Supabase');
|
|
||||||
@@ -61,4 +61,3 @@ export const getAllSchemes = async (organizationId?: string) => {
|
|||||||
return data || [];
|
return data || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ PaintingSchemeService loaded with Supabase');
|
|
||||||
@@ -36,4 +36,3 @@ export const getPartsByOrganization = async (organizationId: string) => {
|
|||||||
return await Part.find({ organization_id: organizationId });
|
return await Part.find({ organization_id: organizationId });
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ PartService loaded with compatibility');
|
|
||||||
|
|||||||
@@ -108,9 +108,18 @@ export const updateProject = async (id: string, data: Partial<ProjectData> & { o
|
|||||||
const updateData: any = { ...data };
|
const updateData: any = { ...data };
|
||||||
delete updateData.organizationId;
|
delete updateData.organizationId;
|
||||||
|
|
||||||
if (data.startDate) updateData.start_date = new Date(data.startDate).toISOString();
|
if (data.startDate !== undefined) {
|
||||||
if (data.endDate) updateData.end_date = new Date(data.endDate).toISOString();
|
updateData.start_date = data.startDate ? new Date(data.startDate).toISOString() : null;
|
||||||
if (data.weightKg) updateData.weight_kg = data.weightKg;
|
delete updateData.startDate;
|
||||||
|
}
|
||||||
|
if (data.endDate !== undefined) {
|
||||||
|
updateData.end_date = data.endDate ? new Date(data.endDate).toISOString() : null;
|
||||||
|
delete updateData.endDate;
|
||||||
|
}
|
||||||
|
if (data.weightKg !== undefined) {
|
||||||
|
updateData.weight_kg = data.weightKg;
|
||||||
|
delete updateData.weightKg;
|
||||||
|
}
|
||||||
|
|
||||||
const { data: updated, error } = await supabase
|
const { data: updated, error } = await supabase
|
||||||
.from('projects')
|
.from('projects')
|
||||||
@@ -154,5 +163,3 @@ export const getProjectStats = async (projectId: string) => {
|
|||||||
parts: partsRes.data || []
|
parts: partsRes.data || []
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ ProjectService loaded with Supabase');
|
|
||||||
Reference in New Issue
Block a user