🚀 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();
|
||||
|
||||
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';
|
||||
|
||||
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 express from 'express';
|
||||
import cors from 'cors';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
// Import local database connection that sets up GridFS/Bucket
|
||||
import { connectDB } from '../../src/server/config/database.js';
|
||||
@@ -50,8 +49,7 @@ app.use('/uploads', uploadsRoutes);
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date(),
|
||||
mongoState: mongoose.connection.readyState
|
||||
timestamp: new Date()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Generated
+1191
-593
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,8 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"enhanced-resolve": "^5.18.4",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.6.2",
|
||||
"helmet": "^8.3.0",
|
||||
"jose": "^5.2.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mongodb": "^7.1.1",
|
||||
|
||||
@@ -2,26 +2,18 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import type { AppUser } from '../types';
|
||||
import { AuthContext } from './AuthContextType';
|
||||
import { setApiOrganizationId } from '../services/api';
|
||||
import api from '../services/api';
|
||||
|
||||
interface AuthProviderProps {
|
||||
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_NAME = 'Organização Padrão';
|
||||
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const [appUser, setAppUser] = useState<AppUser | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const storedUser = localStorage.getItem('gpi_user');
|
||||
@@ -34,25 +26,26 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
}
|
||||
|
||||
setApiOrganizationId(DEFAULT_ORGANIZATION_ID, DEFAULT_ORGANIZATION_NAME);
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const signInWithPassword = async (password: string): Promise<boolean> => {
|
||||
if (password === '@@Gi05Br;;') {
|
||||
const adminUser: AppUser = {
|
||||
...defaultUser,
|
||||
id: 'admin-001',
|
||||
email: 'admtracksteel@gmail.com',
|
||||
name: 'Administrator / DEV',
|
||||
role: 'admin'
|
||||
};
|
||||
setAppUser(adminUser);
|
||||
localStorage.setItem('gpi_user', JSON.stringify(adminUser));
|
||||
return true;
|
||||
try {
|
||||
const response = await api.post('/users/login', { password });
|
||||
if (response.data.success) {
|
||||
const adminUser = response.data.user as AppUser;
|
||||
setAppUser(adminUser);
|
||||
localStorage.setItem('gpi_user', JSON.stringify(adminUser));
|
||||
localStorage.setItem('gpi_token', response.data.token);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
}
|
||||
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 isUser = useCallback(() => !!appUser, [appUser]);
|
||||
const isGuest = useCallback(() => !appUser, [appUser]);
|
||||
@@ -61,7 +54,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
|
||||
const value = useMemo(() => ({
|
||||
appUser,
|
||||
isLoading: false,
|
||||
isLoading,
|
||||
isSignedIn: !!appUser,
|
||||
error: null,
|
||||
isAdmin,
|
||||
@@ -71,7 +64,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
canEdit,
|
||||
refetchUser,
|
||||
signInWithPassword
|
||||
}), [appUser, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser, signInWithPassword]);
|
||||
}), [appUser, isLoading, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
|
||||
+5
-8
@@ -3,20 +3,17 @@ import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
export function getToken() {
|
||||
return 'guest-token';
|
||||
return localStorage.getItem('gpi_token');
|
||||
}
|
||||
|
||||
export function getUser() {
|
||||
return {
|
||||
id: 'guest-user',
|
||||
email: 'guest@gpi.app',
|
||||
name: 'Guest User',
|
||||
role: 'user'
|
||||
};
|
||||
const user = localStorage.getItem('gpi_user');
|
||||
return user ? JSON.parse(user) : null;
|
||||
}
|
||||
|
||||
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 />)
|
||||
@@ -1,7 +1,6 @@
|
||||
// API service configuration v2.0 - Logto Auth
|
||||
import axios from 'axios';
|
||||
import { triggerGuestWarning } from '../utils/toastHandler';
|
||||
import { getToken } from '../main';
|
||||
|
||||
export const getBaseUrl = () => {
|
||||
if (import.meta.env.VITE_API_URL) {
|
||||
@@ -29,7 +28,7 @@ export const setApiOrganizationId = setApiOrgData;
|
||||
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = getToken();
|
||||
const token = localStorage.getItem('gpi_token');
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
+67
-32
@@ -1,5 +1,12 @@
|
||||
import express from 'express';
|
||||
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 partRoutes from './routes/partRoutes.js';
|
||||
import paintingSchemeRoutes from './routes/paintingSchemeRoutes.js';
|
||||
@@ -11,76 +18,104 @@ import yieldStudyRoutes from './routes/yieldStudyRoutes.js';
|
||||
import userRoutes from './routes/userRoutes.js';
|
||||
import systemSettingsRoutes from './routes/systemSettingsRoutes.js';
|
||||
import geometryTypeRoutes from './routes/geometryTypeRoutes.js';
|
||||
|
||||
import stockRoutes from './routes/stockRoutes.js';
|
||||
import notificationRoutes from './routes/notificationRoutes.js';
|
||||
import instrumentRoutes from './routes/instrumentRoutes.js';
|
||||
import messageRoutes from './routes/messageRoutes.js';
|
||||
import backupRoutes from './routes/backupRoutes.js';
|
||||
import path from 'path';
|
||||
|
||||
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({
|
||||
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'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id']
|
||||
}));
|
||||
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
import { extractUser } from './middleware/authMiddleware.js';
|
||||
app.use(extractUser);
|
||||
app.use(express.json({ limit: '5mb' }));
|
||||
app.use(express.urlencoded({ limit: '5mb', extended: true }));
|
||||
|
||||
// Static Uploads
|
||||
import fs from 'fs';
|
||||
const uploadsPath = path.join(process.cwd(), 'uploads');
|
||||
|
||||
if (!fs.existsSync(uploadsPath)) {
|
||||
fs.mkdirSync(uploadsPath, { recursive: true });
|
||||
}
|
||||
|
||||
app.use('/uploads', express.static(uploadsPath));
|
||||
|
||||
// Serve frontend static files
|
||||
const distPath = path.join(process.cwd(), 'dist');
|
||||
app.use(express.static(distPath));
|
||||
|
||||
// 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/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.use('/api/messages', messageRoutes);
|
||||
app.use('/api/backup', backupRoutes);
|
||||
// API Routes (all API routes pass through extractUser here, avoid duplication in route files)
|
||||
const apiRouter = express.Router();
|
||||
apiRouter.use(extractUser);
|
||||
|
||||
apiRouter.use('/users', userRoutes);
|
||||
apiRouter.use('/projects', projectRoutes);
|
||||
apiRouter.use('/parts', partRoutes);
|
||||
apiRouter.use('/painting-schemes', paintingSchemeRoutes);
|
||||
apiRouter.use('/application-records', applicationRecordRoutes);
|
||||
apiRouter.use('/inspections', inspectionRoutes);
|
||||
apiRouter.use('/', analysisRoutes);
|
||||
apiRouter.use('/datasheets', dataSheetRoutes);
|
||||
apiRouter.use('/yield-studies', yieldStudyRoutes);
|
||||
apiRouter.use('/system-settings', systemSettingsRoutes);
|
||||
apiRouter.use('/geometry-types', geometryTypeRoutes);
|
||||
apiRouter.use('/stock', stockRoutes);
|
||||
apiRouter.use('/notifications', notificationRoutes);
|
||||
apiRouter.use('/instruments', instrumentRoutes);
|
||||
apiRouter.use('/messages', messageRoutes);
|
||||
apiRouter.use('/backup', backupRoutes);
|
||||
|
||||
app.use('/api', apiRouter);
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
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
|
||||
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'));
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
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' });
|
||||
});
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createClient } from '@supabase/supabase-js';
|
||||
import dotenv from 'dotenv';
|
||||
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;
|
||||
|
||||
if (!supabaseServiceKey) {
|
||||
throw new Error('❌ SUPABASE_SERVICE_ROLE_KEY is missing in environment variables');
|
||||
if (!supabaseUrl || !supabaseServiceKey) {
|
||||
throw new Error('❌ SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY must be provided in environment variables');
|
||||
}
|
||||
|
||||
export const GPI_SCHEMA = 'public';
|
||||
@@ -68,5 +68,3 @@ export async function findOneGpi(table: string, filters: Record<string, any>) {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
console.log('✅ Supabase client initialized');
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
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
|
||||
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.' });
|
||||
}
|
||||
};
|
||||
|
||||
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 { 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;
|
||||
},
|
||||
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;
|
||||
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 { authenticateRequest } from './logtoAuth.js';
|
||||
|
||||
export interface IAppUser {
|
||||
id: string;
|
||||
@@ -16,22 +17,47 @@ declare module 'express-serve-static-core' {
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_ORG_ID = 'e47e6210-4879-4e5b-bf21-9285d2713123';
|
||||
|
||||
const GUEST_USER: IAppUser = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
logtoId: 'guest',
|
||||
email: 'guest@gpi.app',
|
||||
name: 'Guest User',
|
||||
role: 'guest',
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
organizationRole: 'guest'
|
||||
};
|
||||
|
||||
export const extractUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
req.appUser = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
logtoId: 'guest',
|
||||
email: 'guest@gpi.app',
|
||||
name: 'Guest User',
|
||||
role: 'user',
|
||||
organizationId: req.headers['x-organization-id'] as string || 'e47e6210-4879-4e5b-bf21-9285d2713123',
|
||||
organizationRole: 'user'
|
||||
};
|
||||
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();
|
||||
};
|
||||
|
||||
export const requireRole = (allowedRoles: string[]) => {
|
||||
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();
|
||||
};
|
||||
};
|
||||
@@ -40,9 +66,15 @@ export const requireAdmin = requireRole(['admin']);
|
||||
export const requireUser = requireRole(['user', 'admin']);
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Router } from 'express';
|
||||
import * as appRecordController from '../controllers/applicationRecordController.js';
|
||||
import { extractUser, requireUser } from '../middleware/authMiddleware.js';
|
||||
import { requireUser } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Public routes (read-only)
|
||||
router.get('/project/:projectId', extractUser, appRecordController.getApplicationRecordsByProject);
|
||||
router.get('/project/:projectId', appRecordController.getApplicationRecordsByProject);
|
||||
|
||||
// Protected routes (require user permission)
|
||||
router.post('/', extractUser, requireUser, appRecordController.createApplicationRecord);
|
||||
router.put('/:id', extractUser, requireUser, appRecordController.updateApplicationRecord);
|
||||
router.delete('/:id', extractUser, requireUser, appRecordController.deleteApplicationRecord);
|
||||
router.post('/', requireUser, appRecordController.createApplicationRecord);
|
||||
router.put('/:id', requireUser, appRecordController.updateApplicationRecord);
|
||||
router.delete('/:id', requireUser, appRecordController.deleteApplicationRecord);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
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 path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -42,15 +42,15 @@ const upload = multer({
|
||||
});
|
||||
|
||||
// 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)
|
||||
router.get('/', extractUser, inspectionController.getAllInspections);
|
||||
router.get('/project/:projectId', extractUser, inspectionController.getInspectionsByProject);
|
||||
router.get('/', inspectionController.getAllInspections);
|
||||
router.get('/project/:projectId', inspectionController.getInspectionsByProject);
|
||||
|
||||
// Protected routes (require user permission)
|
||||
router.post('/', extractUser, requireUser, inspectionController.createInspection);
|
||||
router.put('/:id', extractUser, requireUser, inspectionController.updateInspection);
|
||||
router.delete('/:id', extractUser, requireUser, inspectionController.deleteInspection);
|
||||
router.post('/', requireUser, inspectionController.createInspection);
|
||||
router.put('/:id', requireUser, inspectionController.updateInspection);
|
||||
router.delete('/:id', requireUser, inspectionController.deleteInspection);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import express from 'express';
|
||||
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();
|
||||
|
||||
// All routes require authentication
|
||||
router.post('/', extractUser, sendMessage);
|
||||
router.get('/unread', extractUser, getUnreadMessages);
|
||||
router.get('/pending', extractUser, getMyPendingMessages);
|
||||
router.patch('/:id/read', extractUser, markMessageAsRead);
|
||||
router.patch('/:id/archive', extractUser, archiveMessage);
|
||||
router.delete('/:id', extractUser, deleteMessage); // Sender side (if unread)
|
||||
router.delete('/:id/recipient', extractUser, recipientDeleteMessage); // Recipient side
|
||||
router.post('/', sendMessage);
|
||||
router.get('/unread', getUnreadMessages);
|
||||
router.get('/pending', getMyPendingMessages);
|
||||
router.patch('/:id/read', markMessageAsRead);
|
||||
router.patch('/:id/archive', archiveMessage);
|
||||
router.delete('/:id', deleteMessage); // Sender side (if unread)
|
||||
router.delete('/:id/recipient', recipientDeleteMessage); // Recipient side
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Router } from 'express';
|
||||
import * as partController from '../controllers/partController.js';
|
||||
import { extractUser, requireAdmin } from '../middleware/authMiddleware.js';
|
||||
import { requireAdmin } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Public routes (read-only)
|
||||
router.get('/', extractUser, partController.getAllParts);
|
||||
router.get('/project/:projectId', extractUser, partController.getPartsByProject);
|
||||
router.get('/', partController.getAllParts);
|
||||
router.get('/project/:projectId', partController.getPartsByProject);
|
||||
|
||||
// Protected routes (require admin permission)
|
||||
router.post('/', extractUser, requireAdmin, partController.createPart);
|
||||
router.put('/:id', extractUser, requireAdmin, partController.updatePart);
|
||||
router.delete('/:id', extractUser, requireAdmin, partController.deletePart);
|
||||
router.post('/', requireAdmin, partController.createPart);
|
||||
router.put('/:id', requireAdmin, partController.updatePart);
|
||||
router.delete('/:id', requireAdmin, partController.deletePart);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Router } from 'express';
|
||||
import * as projectController from '../controllers/projectController.js';
|
||||
import { extractUser, requireAdmin } from '../middleware/authMiddleware.js';
|
||||
import { requireAdmin } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Rotas com contexto de usuário (leitura)
|
||||
router.get('/', extractUser, projectController.getAllProjects);
|
||||
router.get('/dashboard', extractUser, projectController.getDashboardProjects);
|
||||
router.get('/:id', extractUser, projectController.getProjectById);
|
||||
router.get('/', projectController.getAllProjects);
|
||||
router.get('/dashboard', projectController.getDashboardProjects);
|
||||
router.get('/:id', projectController.getProjectById);
|
||||
|
||||
// Protected routes (require admin permission)
|
||||
router.post('/', extractUser, requireAdmin, projectController.createProject);
|
||||
router.put('/:id', extractUser, requireAdmin, projectController.updateProject);
|
||||
router.delete('/:id', extractUser, requireAdmin, projectController.deleteProject);
|
||||
router.patch('/:id/archive', extractUser, requireAdmin, projectController.archiveProject);
|
||||
router.post('/', requireAdmin, projectController.createProject);
|
||||
router.put('/:id', requireAdmin, projectController.updateProject);
|
||||
router.delete('/:id', requireAdmin, projectController.deleteProject);
|
||||
router.patch('/:id/archive', requireAdmin, projectController.archiveProject);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from 'express';
|
||||
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';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -9,10 +9,10 @@ const router = express.Router();
|
||||
router.get('/', getSettings);
|
||||
|
||||
// Protected update
|
||||
router.put('/', extractUser, requireDeveloper, updateSettings);
|
||||
router.put('/', requireDeveloper, updateSettings);
|
||||
|
||||
// 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)
|
||||
router.get('/logo-image/:filename', (req, res) => {
|
||||
@@ -23,8 +23,8 @@ router.get('/logo-image/:filename', (req, res) => {
|
||||
// Global Admin Routes
|
||||
import { getGlobalUsers, getGlobalOrganizations, toggleOrganizationBan } from '../controllers/systemSettingsController.js';
|
||||
|
||||
router.get('/users', extractUser, requireDeveloper, getGlobalUsers);
|
||||
router.get('/organizations', extractUser, requireDeveloper, getGlobalOrganizations);
|
||||
router.post('/organizations/ban', extractUser, requireDeveloper, toggleOrganizationBan);
|
||||
router.get('/users', requireDeveloper, getGlobalUsers);
|
||||
router.get('/organizations', requireDeveloper, getGlobalOrganizations);
|
||||
router.post('/organizations/ban', requireDeveloper, toggleOrganizationBan);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import express from 'express';
|
||||
import { syncUser, getCurrentUser, getAllUsers, updateUserRole, toggleBanUser, heartbeat, getActiveUsers, deleteUser } from '../controllers/userController.js';
|
||||
import { extractUser } from '../middleware/authMiddleware.js';
|
||||
import { syncUser, getCurrentUser, getAllUsers, updateUserRole, toggleBanUser, heartbeat, getActiveUsers, deleteUser, login } from '../controllers/userController.js';
|
||||
import { requireAdmin, requireUser } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Public routes (no auth required)
|
||||
router.get('/', getAllUsers);
|
||||
router.get('/me', extractUser, getCurrentUser);
|
||||
// Public routes (no auth required originally, but now all API has extractUser)
|
||||
// Only users should see all users
|
||||
router.get('/', requireUser, getAllUsers);
|
||||
router.get('/me', getCurrentUser);
|
||||
router.post('/login', login);
|
||||
|
||||
// Heartbeat & Presence
|
||||
router.post('/heartbeat', extractUser, heartbeat);
|
||||
router.get('/active', extractUser, getActiveUsers);
|
||||
router.post('/heartbeat', heartbeat);
|
||||
router.get('/active', getActiveUsers);
|
||||
|
||||
// Admin routes
|
||||
router.patch('/:id/role', extractUser, updateUserRole);
|
||||
router.patch('/:id/ban', extractUser, toggleBanUser);
|
||||
router.delete('/:id', extractUser, deleteUser);
|
||||
router.patch('/:id/role', requireAdmin, updateUserRole);
|
||||
router.patch('/:id/ban', requireAdmin, toggleBanUser);
|
||||
router.delete('/:id', requireAdmin, deleteUser);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -60,17 +60,10 @@ function getContentType(ext: string): string {
|
||||
return types[ext.toLowerCase()] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
export const saveFileToGridFS = saveFileToStorage;
|
||||
export const deleteFileFromGridFS = deleteFileFromStorage;
|
||||
|
||||
export const getFileStream = (fileUrl: string) => {
|
||||
return fileUrl;
|
||||
};
|
||||
|
||||
export const migrateFilesToGridFS = async () => {
|
||||
console.log('ℹ️ File migration skipped - using Supabase Storage');
|
||||
};
|
||||
|
||||
export const getAllDataSheets = async (organizationId?: string) => {
|
||||
try {
|
||||
let query = supabase.from('technical_data_sheets').select('*');
|
||||
@@ -138,4 +131,3 @@ export const deleteDataSheet = async (id: string, organizationId?: string) => {
|
||||
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 || [];
|
||||
};
|
||||
|
||||
console.log('✅ PaintingSchemeService loaded with Supabase');
|
||||
@@ -36,4 +36,3 @@ export const getPartsByOrganization = async (organizationId: string) => {
|
||||
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 };
|
||||
delete updateData.organizationId;
|
||||
|
||||
if (data.startDate) updateData.start_date = new Date(data.startDate).toISOString();
|
||||
if (data.endDate) updateData.end_date = new Date(data.endDate).toISOString();
|
||||
if (data.weightKg) updateData.weight_kg = data.weightKg;
|
||||
if (data.startDate !== undefined) {
|
||||
updateData.start_date = data.startDate ? new Date(data.startDate).toISOString() : null;
|
||||
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
|
||||
.from('projects')
|
||||
@@ -154,5 +163,3 @@ export const getProjectStats = async (projectId: string) => {
|
||||
parts: partsRes.data || []
|
||||
};
|
||||
};
|
||||
|
||||
console.log('✅ ProjectService loaded with Supabase');
|
||||
Reference in New Issue
Block a user