diff --git a/package-lock.json b/package-lock.json index 45da74d..6ea7c7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "express": "^5.2.1", "jose": "^5.2.0", "lucide-react": "^0.562.0", - "mongodb": "^7.1.1", "multer": "^2.0.2", "pdf-parse": "^1.1.1", "prop-types": "^15.8.1", @@ -56,7 +55,6 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", - "mongoose": "^8.23.0", "nodemon": "^3.1.11", "postcss": "^8.5.6", "tailwindcss": "^4.1.18", @@ -2391,13 +2389,6 @@ "node": ">=10" } }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.4.6", - "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -3604,17 +3595,6 @@ "version": "10.0.0", "license": "MIT" }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "13.0.0", - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, "node_modules/@types/ws": { "version": "8.18.1", "license": "MIT", @@ -5042,13 +5022,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bson": { - "version": "7.2.0", - "license": "Apache-2.0", - "engines": { - "node": ">=20.19.0" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "license": "MIT" @@ -8037,10 +8010,6 @@ "node": ">= 0.8" } }, - "node_modules/memory-pager": { - "version": "1.5.0", - "license": "MIT" - }, "node_modules/merge-descriptors": { "version": "2.0.0", "license": "MIT", @@ -8164,179 +8133,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/mongodb": { - "version": "7.1.1", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.3.0", - "bson": "^7.1.1", - "mongodb-connection-string-url": "^7.0.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.806.0", - "@mongodb-js/zstd": "^7.0.0", - "gcp-metadata": "^7.0.1", - "kerberos": "^7.0.0", - "mongodb-client-encryption": ">=7.0.0 <7.1.0", - "snappy": "^7.3.2", - "socks": "^2.8.6" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "7.0.1", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^13.0.0", - "whatwg-url": "^14.1.0" - }, - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/mongoose": { - "version": "8.23.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.20.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/node_modules/@types/whatwg-url": { - "version": "11.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/mongoose/node_modules/bson": { - "version": "6.10.4", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/mongoose/node_modules/kareem": { - "version": "2.6.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.20.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.3.0", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.2" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.3.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongoose/node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" - } - }, - "node_modules/mongoose/node_modules/mquery": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/mpath": { - "version": "0.9.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mri": { "version": "1.2.0", "dev": true, @@ -8973,6 +8769,7 @@ }, "node_modules/punycode": { "version": "2.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9791,11 +9588,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sift": { - "version": "17.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/signal-exit": { "version": "4.1.0", "license": "ISC", @@ -9903,13 +9695,6 @@ "dev": true, "license": "MIT" }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "license": "MIT", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, "node_modules/statuses": { "version": "2.0.2", "license": "MIT", @@ -10299,16 +10084,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "5.1.1", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -11351,24 +11126,6 @@ "version": "1.8.0", "license": "Apache-2.0" }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "license": "ISC", diff --git a/package.json b/package.json index 1ce6d38..b43e3f5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "express": "^5.2.1", "jose": "^5.2.0", "lucide-react": "^0.562.0", - "mongodb": "^7.1.1", "multer": "^2.0.2", "pdf-parse": "^1.1.1", "prop-types": "^15.8.1", @@ -65,7 +64,6 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", - "mongoose": "^8.23.0", "nodemon": "^3.1.11", "postcss": "^8.5.6", "tailwindcss": "^4.1.18", diff --git a/src/client/App.tsx b/src/client/App.tsx index 1d4a147..b6ed1ed 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -17,8 +17,6 @@ import { DeveloperDashboard } from './pages/DeveloperDashboard'; import { CalculatorDashboard } from './pages/CalculatorDashboard'; import { StockDashboard } from './pages/StockDashboard'; import { GuestDashboard } from './pages/GuestDashboard'; -import { Login } from './pages/Login'; -import { Callback } from './pages/Callback'; import InstrumentList from './pages/InstrumentList'; const DeveloperRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { @@ -39,8 +37,6 @@ const AppContent: React.FC = () => { } /> - } /> - } /> } /> } /> } /> diff --git a/src/client/components/Layout.tsx b/src/client/components/Layout.tsx index 4dd0794..c994df1 100644 --- a/src/client/components/Layout.tsx +++ b/src/client/components/Layout.tsx @@ -2,9 +2,8 @@ import React, { useState } from 'react'; import NotificationBell from './NotificationBell'; import { TeamPresence } from './TeamPresence'; import { Link, useLocation, useNavigate } from 'react-router-dom'; -import { Menu, X, FolderOpen, Layers, ClipboardCheck, LogOut, TrendingUp, Sun, Moon, HelpCircle, Shield, Wrench, Terminal, LayoutDashboard, Package, Thermometer, User } from 'lucide-react'; +import { Menu, X, FolderOpen, Layers, ClipboardCheck, TrendingUp, Sun, Moon, HelpCircle, Shield, Wrench, Terminal, LayoutDashboard, Package, Thermometer, User } from 'lucide-react'; import { clsx } from 'clsx'; -import { useLogto } from '@logto/react'; import { TechnicalManual } from './TechnicalManual'; import { useAuth } from '../context/useAuth'; @@ -20,8 +19,7 @@ export const Layout: React.FC = ({ children }) => { return saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches); }); const location = useLocation(); - const { signOut } = useLogto(); - const { isAdmin, isUser, isDeveloper, appUser, isSignedIn } = useAuth(); + const { isAdmin, isUser, isDeveloper, appUser } = useAuth(); // Helper to get role display name const getRoleDisplay = () => { @@ -54,14 +52,6 @@ export const Layout: React.FC = ({ children }) => { } }, [appUser, location.pathname, navigate]); - // Redirect to login if not signed in (except for login and callback pages) - React.useEffect(() => { - const publicPaths = ['/login', '/callback']; - if (!isSignedIn && !publicPaths.includes(location.pathname)) { - navigate('/login'); - } - }, [isSignedIn, location.pathname, navigate]); - interface NavItem { icon: React.ElementType; label: string; @@ -87,10 +77,6 @@ export const Layout: React.FC = ({ children }) => { return false; }; - const handleLogout = () => { - signOut(window.location.origin); - }; - if (location.pathname === '/login' || location.pathname === '/callback') { return <>{children}; } @@ -216,14 +202,6 @@ export const Layout: React.FC = ({ children }) => { )} - -
@@ -348,13 +326,6 @@ export const Layout: React.FC = ({ children }) => { {isDarkMode ? : } {isDarkMode ? 'Modo Claro' : 'Modo Escuro'} -
diff --git a/src/client/components/ProtectedRoute.tsx b/src/client/components/ProtectedRoute.tsx index abb04c0..3bf9dff 100644 --- a/src/client/components/ProtectedRoute.tsx +++ b/src/client/components/ProtectedRoute.tsx @@ -23,21 +23,7 @@ export const ProtectedRoute: React.FC = ({ requireEdit = false, redirectTo = '/', }) => { - const { appUser, isLoading, canEdit, isSignedIn } = useAuth(); - - // Show loading state - if (isLoading) { - return ( -
- -
- ); - } - - // Check authentication - if (!isSignedIn) { - return ; - } + const { appUser, canEdit } = useAuth(); // Check role-based access if (allowedRoles && appUser && !allowedRoles.includes(appUser.role)) { diff --git a/src/client/context/AuthContext.tsx b/src/client/context/AuthContext.tsx index 24dd733..a70793c 100644 --- a/src/client/context/AuthContext.tsx +++ b/src/client/context/AuthContext.tsx @@ -1,124 +1,57 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; -import { useLogto } from '@logto/react'; -import type { AppUser } from '../types'; +import type { AppUser, UserRole } from '../types'; import { AuthContext } from './AuthContextType'; -import { setUser } from '../main'; - -const API_URL = import.meta.env.VITE_API_URL || '/api'; +import { getUser } from '../main'; interface AuthProviderProps { children: React.ReactNode; } +const defaultUser: AppUser = { + id: 'guest-user', + email: 'guest@gpi.app', + name: 'Guest User', + role: 'user', + isBanned: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() +}; + export const AuthProvider: React.FC = ({ children }) => { - const { isAuthenticated, getAccessToken, fetchUserInfo, isLoading: isLogtoLoading } = useLogto(); const [appUser, setAppUser] = useState(null); - const [isAppLoading, setIsAppLoading] = useState(true); - const [error, setError] = useState(null); - - const syncUser = useCallback(async () => { - if (!isAuthenticated) { - setAppUser(null); - setIsAppLoading(false); - return; - } - - try { - setIsAppLoading(true); - setError(null); - - const token = await getAccessToken(); - if (!token) throw new Error('Token não disponível'); - - // Busca dados básicos do Logto se necessário - const logtoUserInfo = await fetchUserInfo(); - - const response = await fetch(`${API_URL}/users/me`, { - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - - if (response.status === 404 && logtoUserInfo) { - // Usuário não existe no banco (provavelmente redirecionamento pós-login) - // Vamos tentar sincronizar/provisionar - const syncResp = await fetch(`${API_URL}/users/sync`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email: logtoUserInfo.email, - name: logtoUserInfo.name || logtoUserInfo.username || 'Usuário Logto', - logto_id: logtoUserInfo.sub - }) - }); - - if (!syncResp.ok) throw new Error('Falha ao sincronizar usuário'); - - // Tenta buscar novamente após o sync - return syncUser(); - } - - if (!response.ok) { - throw new Error('Falha ao carregar usuário'); - } - - const userData = await response.json(); - const effectiveRole = userData.role || 'guest'; - - const user = { - ...userData, - id: userData._id || userData.id, - role: effectiveRole, - }; - - setUser(token, user); - setAppUser(user); - } catch (err) { - console.error('Error loading user:', err); - setError('Erro ao carregar dados do usuário'); - setAppUser(null); - } finally { - setIsAppLoading(false); - } - }, [isAuthenticated, getAccessToken, fetchUserInfo]); useEffect(() => { - if (!isLogtoLoading) { - syncUser(); + const storedUser = getUser(); + if (storedUser) { + setAppUser({ ...defaultUser, ...storedUser, role: storedUser.role as UserRole }); + } else { + setAppUser(defaultUser); } - }, [isLogtoLoading, syncUser]); + }, []); - const refetchUser = useCallback(async () => { - await syncUser(); - }, [syncUser]); - - const isDeveloper = useCallback(() => { - return appUser?.email === 'admtracksteel@gmail.com'; - }, [appUser]); - - const isAdmin = useCallback(() => appUser?.role === 'admin' || isDeveloper(), [appUser, isDeveloper]); - const isUser = useCallback(() => appUser?.role === 'user' || isAdmin(), [appUser, isAdmin]); - const isGuest = useCallback(() => appUser?.role === 'guest' && !isDeveloper(), [appUser, isDeveloper]); - const canEdit = useCallback(() => (appUser?.role !== 'guest' && appUser?.role !== undefined) || isDeveloper(), [appUser, isDeveloper]); - - const isLoading = isLogtoLoading || isAppLoading; + const isDeveloper = useCallback(() => false, []); + const isAdmin = useCallback(() => true, []); + const isUser = useCallback(() => true, []); + const isGuest = useCallback(() => false, []); + const canEdit = useCallback(() => true, []); + const refetchUser = useCallback(async () => {}, []); const value = useMemo(() => ({ appUser, - isLoading, - isSignedIn: isAuthenticated, - error, + isLoading: false, + isSignedIn: true, + error: null, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser, - }), [appUser, isLoading, isAuthenticated, error, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser]); + }), [appUser, isAdmin, isUser, isGuest, isDeveloper, canEdit, refetchUser]); return ( {children} ); -}; +}; \ No newline at end of file diff --git a/src/client/main.tsx b/src/client/main.tsx index e48d0d5..aa3ee07 100644 --- a/src/client/main.tsx +++ b/src/client/main.tsx @@ -1,34 +1,22 @@ import { createRoot } from 'react-dom/client' -import { LogtoProvider, type LogtoConfig } from '@logto/react'; import './index.css' import App from './App.tsx' -const LOGTO_URL = import.meta.env.VITE_LOGTO_URL || 'https://logto-admin-bzlued1boxl3t8ewsyn99an9.187.77.227.172.sslip.io'; -const APP_ID = import.meta.env.VITE_LOGTO_APP_ID || 'gpi-app-final'; - -const config: LogtoConfig = { - endpoint: LOGTO_URL, - appId: APP_ID, - scopes: ['openid', 'profile', 'email'], -}; - -// Mantenha estas para compatibilidade temporária se necessário export function getToken() { - return sessionStorage.getItem('logto_token'); + return 'guest-token'; } export function getUser() { - const user = sessionStorage.getItem('logto_user'); - return user ? JSON.parse(user) : null; + return { + id: 'guest-user', + email: 'guest@gpi.app', + name: 'Guest User', + role: 'user' + }; } export function setUser(token: string, user: any) { - sessionStorage.setItem('logto_token', token); - sessionStorage.setItem('logto_user', JSON.stringify(user)); + console.log('User set (no auth):', user); } -createRoot(document.getElementById('root')!).render( - - - -) +createRoot(document.getElementById('root')!).render() \ No newline at end of file diff --git a/src/server/controllers/applicationRecordController.ts b/src/server/controllers/applicationRecordController.ts index 1f3f5fa..3058964 100644 --- a/src/server/controllers/applicationRecordController.ts +++ b/src/server/controllers/applicationRecordController.ts @@ -5,7 +5,7 @@ import '../middleware/authMiddleware.js'; // Ensure type augmentation export const createApplicationRecord = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const createdBy = req.appUser?.clerkId; + const createdBy = req.appUser?.email || 'guest'; const record = await appRecordService.createApplicationRecord({ ...req.body, organizationId, createdBy }); res.status(201).json(record); } catch (error: unknown) { @@ -29,17 +29,10 @@ export const getApplicationRecordsByProject = async (req: Request, res: Response export const updateApplicationRecord = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const userId = req.appUser?.clerkId; - const userRole = req.appUser?.organizationRole || req.appUser?.role; - const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; const record = await appRecordService.updateApplicationRecord( req.params.id as string, - req.body, - organizationId, - userId, - userRole as any, - isDeveloper + req.body ); if (!record) return res.status(403).json({ error: 'Não autorizado ou não encontrado.' }); res.json(record); @@ -51,17 +44,8 @@ export const updateApplicationRecord = async (req: Request, res: Response) => { export const deleteApplicationRecord = async (req: Request, res: Response) => { try { - const organizationId = req.appUser?.organizationId; - const userId = req.appUser?.clerkId; - const userRole = req.appUser?.organizationRole || req.appUser?.role; - const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; - const success = await appRecordService.deleteApplicationRecord( - req.params.id as string, - organizationId, - userId, - userRole as any, - isDeveloper + req.params.id as string ); if (!success) return res.status(403).json({ error: 'Não autorizado ou não encontrado.' }); res.status(204).send(); diff --git a/src/server/controllers/inspectionController.ts b/src/server/controllers/inspectionController.ts index b9479d5..7059a69 100644 --- a/src/server/controllers/inspectionController.ts +++ b/src/server/controllers/inspectionController.ts @@ -6,7 +6,7 @@ import '../middleware/authMiddleware.js'; // Ensure type augmentation export const createInspection = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const createdBy = req.appUser?.clerkId; + const createdBy = req.appUser?.email || 'guest'; const inspection = await inspectionService.createInspection({ ...req.body, organizationId, @@ -46,17 +46,10 @@ export const getInspectionsByProject = async (req: Request, res: Response) => { export const updateInspection = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const userId = req.appUser?.clerkId; - const userRole = req.appUser?.organizationRole || req.appUser?.role; - const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; const inspection = await inspectionService.updateInspection( req.params.id as string, - req.body, - organizationId, - userId, - userRole as any, - isDeveloper + req.body ); if (!inspection) return res.status(403).json({ error: 'Não autorizado ou não encontrado.' }); res.json(inspection); @@ -69,16 +62,9 @@ export const updateInspection = async (req: Request, res: Response) => { export const deleteInspection = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const userId = req.appUser?.clerkId; - const userRole = req.appUser?.organizationRole || req.appUser?.role; - const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com'; const success = await inspectionService.deleteInspection( - req.params.id as string, - organizationId, - userId, - userRole as any, - isDeveloper + req.params.id as string ); if (!success) return res.status(403).json({ error: 'Não autorizado ou não encontrado.' }); res.status(204).send(); @@ -91,7 +77,9 @@ export const deleteInspection = async (req: Request, res: Response) => { export const getAllInspections = async (req: Request, res: Response) => { try { const organizationId = req.appUser?.organizationId; - const inspections = await inspectionService.getAllInspections(organizationId); + const inspections = organizationId + ? await inspectionService.getInspectionsByOrganization(organizationId) + : await inspectionService.getInspectionStats(); res.json(inspections); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; diff --git a/src/server/controllers/systemSettingsController.ts b/src/server/controllers/systemSettingsController.ts index cfcab92..d67c6f3 100644 --- a/src/server/controllers/systemSettingsController.ts +++ b/src/server/controllers/systemSettingsController.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { SystemSettings } from '../lib/compat.js'; -import { User, OrganizationMember, Organization } from '../lib/compat.js'; +import { User, Organization, OrganizationMember } from '../lib/compat.js'; +import { supabase } from '../config/supabase.js'; import path from 'path'; import fs from 'fs'; import os from 'os'; @@ -29,16 +30,28 @@ export const updateSettings = async (req: Request, res: Response) => { try { const { appName, appSubtitle, appLogoUrl } = req.body; - const settings = await SystemSettings.findOneAndUpdate( - { settingsId: 'global' }, - { + const existing = await SystemSettings.findOne({ settingsId: 'global' }); + + let settings; + if (!existing) { + settings = await SystemSettings.create({ + settingsId: 'global', appName, appSubtitle, appLogoUrl, updatedBy: req.appUser?.email - }, - { new: true, upsert: true } // Create if not exists - ); + }); + } else { + settings = await SystemSettings.findByIdAndUpdate( + existing.id, + { + appName, + appSubtitle, + appLogoUrl, + updatedBy: req.appUser?.email + } + ); + } console.log(`⚙️ System Settings updated by ${req.appUser?.email}`); res.json(settings); @@ -92,8 +105,13 @@ export const uploadLogo = async (req: Request, res: Response) => { // Global Admin Functions export const getGlobalUsers = async (req: Request, res: Response) => { try { - const users = await User.find({}).sort({ createdAt: -1 }); - res.json(users); + const { data: users, error } = await supabase + .from('users') + .select('*') + .order('created_at', { ascending: false }); + + if (error) throw error; + res.json(users || []); } catch (error) { console.error('Error getting global users:', error); res.status(500).json({ error: 'Erro ao buscar usuários globais.' }); @@ -102,51 +120,28 @@ export const getGlobalUsers = async (req: Request, res: Response) => { export const getGlobalOrganizations = async (req: Request, res: Response) => { try { - // Aggregate members to group by org and get full member lists - const organizations = await OrganizationMember.aggregate([ - { - $group: { - _id: '$organizationId', - members: { - $push: { - name: '$name', - email: '$email', - role: '$role', - clerkUserId: '$clerkUserId', - isBanned: '$isBanned' - } - }, - lastActive: { $max: '$updatedAt' } - } - }, - { - $lookup: { - from: 'organizations', // Ensure this matches the collection name of Organization model - localField: '_id', - foreignField: 'clerkId', - as: 'orgDetails' - } - }, - { - $unwind: { - path: '$orgDetails', - preserveNullAndEmptyArrays: true - } - }, - { - $project: { - _id: 1, - lastActive: 1, - members: 1, - memberCount: { $size: '$members' }, - isBanned: { $ifNull: ['$orgDetails.isBanned', false] }, - name: { $ifNull: ['$orgDetails.name', ''] } - } - }, - { $sort: { memberCount: -1 } } - ]); - - res.json(organizations); + const { data: organizations, error } = await supabase + .from('organizations') + .select('*'); + + if (error) throw error; + + const orgsWithMembers = await Promise.all( + (organizations || []).map(async (org) => { + const { data: members } = await supabase + .from('user_organizations') + .select('*') + .eq('organization_id', org.id); + + return { + ...org, + members: members || [], + memberCount: members?.length || 0 + }; + }) + ); + + res.json(orgsWithMembers); } catch (error) { console.error('Error getting global organizations:', error); res.status(500).json({ error: 'Erro ao buscar organizações globais.' }); @@ -161,12 +156,14 @@ export const toggleOrganizationBan = async (req: Request, res: Response) => { return res.status(400).json({ error: 'ID da organização é obrigatório.' }); } - // Upsert the Organization record - const org = await Organization.findOneAndUpdate( - { clerkId: organizationId }, - { isBanned: isBanned }, - { new: true, upsert: true } - ); + const { data: org, error } = await supabase + .from('organizations') + .update({ is_banned: isBanned }) + .eq('id', organizationId) + .select() + .single(); + + if (error) throw error; console.log(`Organization ${organizationId} ban status set to ${isBanned} by ${req.appUser?.email}`); res.json(org); diff --git a/src/server/middleware/authMiddleware.ts b/src/server/middleware/authMiddleware.ts index 932b9c0..f08eafb 100644 --- a/src/server/middleware/authMiddleware.ts +++ b/src/server/middleware/authMiddleware.ts @@ -1,6 +1,4 @@ import { Request, Response, NextFunction } from 'express'; -import { authenticateRequest } from './logtoAuth.js'; -import { findOneGpi } from '../config/supabase.js'; export interface IAppUser { id: string; @@ -19,24 +17,16 @@ declare module 'express-serve-static-core' { } export const extractUser = async (req: Request, res: Response, next: NextFunction) => { - try { - const authHeader = req.headers.authorization; - - if (!authHeader?.startsWith('Bearer ')) { - return next(); - } - - const user = await authenticateRequest(req); - - if (user) { - req.appUser = user; - } - - next(); - } catch (error) { - console.error('Error extracting user:', error); - next(); - } + req.appUser = { + id: 'guest-user', + logtoId: 'guest', + email: 'guest@gpi.app', + name: 'Guest User', + role: 'user', + organizationId: 'default-org', + organizationRole: 'user' + }; + next(); }; export const requireRole = (allowedRoles: string[]) => { @@ -44,17 +34,6 @@ export const requireRole = (allowedRoles: string[]) => { if (!req.appUser) { return res.status(401).json({ error: 'Autenticação necessária.' }); } - - if (req.appUser.email === 'admtracksteel@gmail.com') { - return next(); - } - - const effectiveRole = req.appUser.role; - - if (!allowedRoles.includes(effectiveRole)) { - return res.status(403).json({ error: 'Acesso negado. Permissões insuficientes.' }); - } - next(); }; }; @@ -63,26 +42,9 @@ export const requireAdmin = requireRole(['admin']); export const requireUser = requireRole(['user', 'admin']); export const canEdit = (req: Request, res: Response, next: NextFunction) => { - if (!req.appUser) { - return res.status(401).json({ error: 'Autenticação necessária.' }); - } - - if (req.appUser.role === 'guest') { - return res.status(403).json({ error: 'Convidados não podem editar.' }); - } - next(); }; export const requireDeveloper = (req: Request, res: Response, next: NextFunction) => { - if (!req.appUser) { - return res.status(401).json({ error: 'Autenticação necessária.' }); - } - - if (req.appUser.email !== 'admtracksteel@gmail.com') { - console.warn(`⛔ Attempted unauthorized developer access by: ${req.appUser.email}`); - return res.status(403).json({ error: 'Acesso restrito ao desenvolvedor.' }); - } - next(); }; diff --git a/src/server/models/ApplicationRecord.ts b/src/server/models/ApplicationRecord.ts deleted file mode 100644 index 183f297..0000000 --- a/src/server/models/ApplicationRecord.ts +++ /dev/null @@ -1,47 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IApplicationRecord extends Document { - organizationId?: string; - createdBy?: string; - projectId: mongoose.Types.ObjectId; - coatStage: string; - pieceDescription?: string | null; - date?: Date | null; - operator?: string | null; - realWeight?: number | null; - volumeUsed?: number | null; - areaPainted?: number | null; - wetThicknessAvg?: number | null; - dryThicknessCalc?: number | null; - method?: string | null; - diluentUsed?: number | null; - notes?: string | null; - items?: { - partId: mongoose.Types.ObjectId; - quantity: number; - }[]; -} - -const ApplicationRecordSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - createdBy: { type: String, index: true }, - projectId: { type: Schema.Types.ObjectId, ref: 'Project', required: true }, - coatStage: { type: String, required: true }, - pieceDescription: { type: String }, // Can be auto-generated or manual name for the Batch - date: { type: Date }, - operator: { type: String }, - realWeight: { type: Number }, - volumeUsed: { type: Number }, - areaPainted: { type: Number }, - wetThicknessAvg: { type: Number }, - dryThicknessCalc: { type: Number }, - method: { type: String }, - diluentUsed: { type: Number }, - notes: { type: String }, - items: [{ - partId: { type: Schema.Types.ObjectId, ref: 'Part' }, - quantity: { type: Number, required: true } - }] -}, { timestamps: true }); - -export default mongoose.models.ApplicationRecord || mongoose.model('ApplicationRecord', ApplicationRecordSchema); diff --git a/src/server/models/GeometryType.ts b/src/server/models/GeometryType.ts deleted file mode 100644 index 4858521..0000000 --- a/src/server/models/GeometryType.ts +++ /dev/null @@ -1,22 +0,0 @@ -import mongoose, { Document, Schema } from 'mongoose'; - -export interface IGeometryType extends Document { - name: string; - efficiencyLoss: number; // Percentage, e.g., 10 for 10% - organizationId: string; - createdAt: Date; - updatedAt: Date; -} - -const GeometryTypeSchema: Schema = new Schema({ - name: { type: String, required: true }, - efficiencyLoss: { type: Number, required: true, default: 0 }, - organizationId: { type: String, required: true, index: true }, -}, { - timestamps: true -}); - -// Compound index to ensure unique names per organization -GeometryTypeSchema.index({ organizationId: 1, name: 1 }, { unique: true }); - -export default mongoose.model('GeometryType', GeometryTypeSchema); diff --git a/src/server/models/Inspection.ts b/src/server/models/Inspection.ts deleted file mode 100644 index f0a9d53..0000000 --- a/src/server/models/Inspection.ts +++ /dev/null @@ -1,73 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IInspection extends Document { - organizationId?: string; - createdBy?: string; // Clerk User ID - projectId: mongoose.Types.ObjectId; - type: 'painting' | 'surface_treatment'; - - // Common - date?: Date | null; - inspector?: string | null; - appearance?: 'approved' | 'rejected' | 'notes' | null; // Unified status - defects?: string | null; // Observations - photos?: string[]; // URLs - partTemperature?: number | null; - weightKg?: number | null; - - // Painting Specific - pieceDescription?: string | null; - epsPoints?: (number | null)[]; - adhesionTest?: string | null; - - // Surface Treatment Specific - batch?: string | null; // Lote - treatmentExecutor?: string | null; - treatmentType?: string | null; // Jateamento, Mecânica... - cleaningDegree?: string | null; // Sa 2.5, St 3... - roughnessReadings?: (number | null)[]; // 5 measurements - flashRust?: string | null; - temperature?: number | null; - relativeHumidity?: number | null; - period?: 'morning' | 'afternoon' | 'night' | null; - applicationRecordId?: mongoose.Types.ObjectId; // Link to specific painting batch - stockItemId?: mongoose.Types.ObjectId; // Link to Stock Item (Paint used) - instrumentId?: mongoose.Types.ObjectId; // Link to Instrument used -} - -const InspectionSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - createdBy: { type: String, index: true }, - projectId: { type: Schema.Types.ObjectId, ref: 'Project', required: true }, - applicationRecordId: { type: Schema.Types.ObjectId, ref: 'ApplicationRecord' }, - stockItemId: { type: Schema.Types.ObjectId, ref: 'StockItem' }, - instrumentId: { type: Schema.Types.ObjectId, ref: 'Instrument' }, - type: { type: String, enum: ['painting', 'surface_treatment'], default: 'painting', index: true }, - - // Common - date: { type: Date }, - inspector: { type: String }, - appearance: { type: String }, // approved, rejected, notes - defects: { type: String }, - photos: [{ type: String }], - partTemperature: { type: Number }, - weightKg: { type: Number }, - - // Painting - pieceDescription: { type: String }, - epsPoints: [{ type: Number }], - adhesionTest: { type: String }, - - // Surface Treatment - batch: { type: String }, - treatmentExecutor: { type: String }, - treatmentType: { type: String }, - cleaningDegree: { type: String }, - roughnessReadings: [{ type: Number }], - flashRust: { type: String }, - temperature: { type: Number }, - relativeHumidity: { type: Number }, - period: { type: String }, -}, { timestamps: true }); - -export default mongoose.models.Inspection || mongoose.model('Inspection', InspectionSchema); diff --git a/src/server/models/Instrument.ts b/src/server/models/Instrument.ts deleted file mode 100644 index dafc002..0000000 --- a/src/server/models/Instrument.ts +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IInstrument extends Document { - organizationId: string; - name: string; - type: string; // Ex: Medidor de Camada, Termo-higrômetro - manufacturer?: string; - modelName?: string; - serialNumber: string; - calibrationDate?: Date; - calibrationExpirationDate?: Date; - certificateUrl?: string; // URL do PDF - status: 'active' | 'inactive' | 'maintenance' | 'expired'; - notes?: string; - createdAt: Date; - updatedAt: Date; -} - -const InstrumentSchema: Schema = new Schema({ - organizationId: { type: String, required: true, index: true }, - name: { type: String, required: true }, - type: { type: String, required: true }, - manufacturer: { type: String }, - modelName: { type: String }, - serialNumber: { type: String, required: true }, - calibrationDate: { type: Date }, - calibrationExpirationDate: { type: Date }, - certificateUrl: { type: String }, - status: { - type: String, - enum: ['active', 'inactive', 'maintenance', 'expired'], - default: 'active' - }, - notes: { type: String } -}, { timestamps: true }); - -// Index para evitar duplicidade de número de série dentro da mesma organização -InstrumentSchema.index({ organizationId: 1, serialNumber: 1 }, { unique: true }); - -export default mongoose.models.Instrument || mongoose.model('Instrument', InstrumentSchema); diff --git a/src/server/models/Message.ts b/src/server/models/Message.ts deleted file mode 100644 index 0dd8324..0000000 --- a/src/server/models/Message.ts +++ /dev/null @@ -1,63 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IMessage extends Document { - organizationId: string; - fromUserId: string; // clerkId do remetente - toUserId: string; // clerkId do destinatário - message: string; - isRead: boolean; - readAt?: Date; - isArchived: boolean; - isDeletedByRecipient: boolean; - createdAt: Date; - updatedAt: Date; -} - -const MessageSchema: Schema = new Schema( - { - organizationId: { - type: String, - required: true, - index: true, - }, - fromUserId: { - type: String, - required: true, - index: true, - }, - toUserId: { - type: String, - required: true, - index: true, - }, - message: { - type: String, - required: true, - maxlength: 255, - }, - isRead: { - type: Boolean, - default: false, - }, - readAt: { - type: Date, - }, - isArchived: { - type: Boolean, - default: false, - }, - isDeletedByRecipient: { - type: Boolean, - default: false, - } - }, - { - timestamps: true, - } -); - -// Compound index for efficient queries -MessageSchema.index({ toUserId: 1, isRead: 1 }); -MessageSchema.index({ fromUserId: 1, toUserId: 1 }); - -export default mongoose.model('Message', MessageSchema); diff --git a/src/server/models/Notification.ts b/src/server/models/Notification.ts deleted file mode 100644 index 5813487..0000000 --- a/src/server/models/Notification.ts +++ /dev/null @@ -1,32 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export type NotificationType = 'info' | 'warning' | 'error' | 'success'; - -export interface INotification extends Document { - organizationId: string; - recipientId?: string; // Se null, é para todos da organização - title: string; - message: string; - type: NotificationType; - isRead: boolean; - isArchived: boolean; - archivedBy: string[]; // IDs dos usuários que arquivaram (para notificações globais) - deletedBy: string[]; // IDs dos usuários que deletaram (para notificações globais) - metadata?: any; // Para guardar IDs de projetos, itens, etc. - createdAt: Date; -} - -const NotificationSchema: Schema = new Schema({ - organizationId: { type: String, required: true, index: true }, - recipientId: { type: String, index: true }, // Opcional - title: { type: String, required: true }, - message: { type: String, required: true }, - type: { type: String, enum: ['info', 'warning', 'error', 'success'], default: 'info' }, - isRead: { type: Boolean, default: false }, - isArchived: { type: Boolean, default: false }, - archivedBy: [{ type: String }], - deletedBy: [{ type: String }], - metadata: { type: Schema.Types.Mixed }, -}, { timestamps: true }); - -export default mongoose.models.Notification || mongoose.model('Notification', NotificationSchema); diff --git a/src/server/models/Organization.ts b/src/server/models/Organization.ts deleted file mode 100644 index 6e83f02..0000000 --- a/src/server/models/Organization.ts +++ /dev/null @@ -1,17 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IOrganization extends Document { - clerkId: string; - name?: string; - isBanned: boolean; - createdAt: Date; - updatedAt: Date; -} - -const OrganizationSchema: Schema = new Schema({ - clerkId: { type: String, required: true, unique: true, index: true }, - name: { type: String }, - isBanned: { type: Boolean, default: false }, -}, { timestamps: true }); - -export default mongoose.models.Organization || mongoose.model('Organization', OrganizationSchema); diff --git a/src/server/models/OrganizationMember.ts b/src/server/models/OrganizationMember.ts deleted file mode 100644 index 04565d6..0000000 --- a/src/server/models/OrganizationMember.ts +++ /dev/null @@ -1,52 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export type OrgRole = 'guest' | 'user' | 'admin'; - -export interface IOrganizationMember extends Document { - clerkUserId: string; - organizationId: string; - role: OrgRole; - isBanned: boolean; - // Denormalized user info for quick access - email: string; - name: string; - createdAt: Date; - updatedAt: Date; -} - -const OrganizationMemberSchema: Schema = new Schema({ - clerkUserId: { - type: String, - required: true, - index: true - }, - organizationId: { - type: String, - required: true, - index: true - }, - role: { - type: String, - enum: ['guest', 'user', 'admin'], - default: 'guest' - }, - isBanned: { - type: Boolean, - default: false - }, - email: { - type: String, - required: true - }, - name: { - type: String, - required: true - } -}, { - timestamps: true -}); - -// Compound index for unique user per organization -OrganizationMemberSchema.index({ clerkUserId: 1, organizationId: 1 }, { unique: true }); - -export default mongoose.models.OrganizationMember || mongoose.model('OrganizationMember', OrganizationMemberSchema); diff --git a/src/server/models/PaintingScheme.ts b/src/server/models/PaintingScheme.ts deleted file mode 100644 index 173c469..0000000 --- a/src/server/models/PaintingScheme.ts +++ /dev/null @@ -1,54 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IPaintingScheme extends Document { - projectId: mongoose.Types.ObjectId; - name: string; - type?: string | null; - coat?: string | null; - solidsVolume?: number | null; - yieldTheoretical?: number | null; - epsMin?: number | null; - epsMax?: number | null; - dilution?: number | null; - manufacturer?: string | null; - color?: string | null; - notes?: string | null; - organizationId?: string; - // Consumption Planning - paintConsumption?: number | null; - thinnerConsumption?: number | null; - paintId?: mongoose.Types.ObjectId | null; // Ref to TechnicalDataSheet - thinnerId?: mongoose.Types.ObjectId | null; // Ref to TechnicalDataSheet - preferredStockItemId?: mongoose.Types.ObjectId | null; // Ref to StockItem (Suggested Batch) -} - -const PaintingSchemeSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - projectId: { type: Schema.Types.ObjectId, ref: 'Project', required: true }, - name: { type: String, required: true }, - type: { type: String }, - coat: { type: String }, - solidsVolume: { type: Number }, - yieldTheoretical: { type: Number }, - epsMin: { type: Number }, - epsMax: { type: Number }, - dilution: { type: Number }, - manufacturer: { type: String }, - color: { type: String }, - notes: { type: String }, - // Consumption Planning - paintConsumption: { type: Number }, - thinnerConsumption: { type: Number }, - paintId: { type: Schema.Types.ObjectId, ref: 'TechnicalDataSheet' }, - thinnerId: { type: Schema.Types.ObjectId, ref: 'TechnicalDataSheet' }, - preferredStockItemId: { type: Schema.Types.ObjectId, ref: 'StockItem' } -}, { strict: false }); - -console.log("✅✅✅ PAINTING SCHEME MODEL (WITH CONSUMPTION) LOADED ✅✅✅"); - -// Force model recompilation to ensure schema updates are applied -if (mongoose.models.PaintingScheme) { - delete mongoose.models.PaintingScheme; -} - -export default mongoose.model('PaintingScheme', PaintingSchemeSchema); diff --git a/src/server/models/Part.ts b/src/server/models/Part.ts deleted file mode 100644 index b22515f..0000000 --- a/src/server/models/Part.ts +++ /dev/null @@ -1,29 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IPart extends Document { - projectId?: mongoose.Types.ObjectId; - description: string; - dimensions?: string | null; - weight?: number | null; - type?: string | null; - area?: number | null; - complexity?: number | null; - quantity: number; - notes?: string | null; - organizationId?: string; -} - -const PartSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - projectId: { type: Schema.Types.ObjectId, ref: 'Project', required: false }, - description: { type: String, required: true }, - dimensions: { type: String }, - weight: { type: Number }, - type: { type: String }, - area: { type: Number }, - complexity: { type: Number }, - quantity: { type: Number, required: true, default: 1 }, - notes: { type: String }, -}); - -export default mongoose.models.Part || mongoose.model('Part', PartSchema); diff --git a/src/server/models/Project.ts b/src/server/models/Project.ts deleted file mode 100644 index e8e4a6e..0000000 --- a/src/server/models/Project.ts +++ /dev/null @@ -1,29 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IProject extends Document { - name: string; - client: string; - startDate?: Date | null; - endDate?: Date | null; - technician?: string | null; - environment?: string | null; - organizationId?: string; - weightKg?: number | null; - status: 'active' | 'archived'; - createdAt: Date; - updatedAt: Date; -} - -const ProjectSchema: Schema = new Schema({ - name: { type: String, required: true }, - client: { type: String, required: true }, - organizationId: { type: String, index: true }, - startDate: { type: Date }, - endDate: { type: Date }, - technician: { type: String }, - environment: { type: String }, - weightKg: { type: Number }, - status: { type: String, enum: ['active', 'archived'], default: 'active', index: true }, -}, { timestamps: true }); - -export default mongoose.models.Project || mongoose.model('Project', ProjectSchema); diff --git a/src/server/models/StockAuditLog.ts b/src/server/models/StockAuditLog.ts deleted file mode 100644 index 53f38ca..0000000 --- a/src/server/models/StockAuditLog.ts +++ /dev/null @@ -1,31 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IStockAuditLog extends Document { - organizationId?: string; - stockItemId: mongoose.Types.ObjectId; - movementId?: mongoose.Types.ObjectId; // Optional, might be deleted - movementNumber?: number; - userId: string; - userName: string; - action: 'CREATE' | 'UPDATE' | 'DELETE'; - details: string; // Human readable summary - oldValues?: Record; - newValues?: Record; - timestamp: Date; -} - -const StockAuditLogSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - stockItemId: { type: Schema.Types.ObjectId, ref: 'StockItem', required: true }, - movementId: { type: Schema.Types.ObjectId, ref: 'StockMovement' }, - movementNumber: { type: Number }, - userId: { type: String, required: true }, - userName: { type: String, required: true }, - action: { type: String, required: true, enum: ['CREATE', 'UPDATE', 'DELETE'] }, - details: { type: String, required: true }, - oldValues: { type: Object }, - newValues: { type: Object }, - timestamp: { type: Date, default: Date.now } -}, { timestamps: true }); - -export default mongoose.models.StockAuditLog || mongoose.model('StockAuditLog', StockAuditLogSchema); diff --git a/src/server/models/StockItem.ts b/src/server/models/StockItem.ts deleted file mode 100644 index 8c60c1c..0000000 --- a/src/server/models/StockItem.ts +++ /dev/null @@ -1,43 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IStockItem extends Document { - organizationId?: string; - createdBy?: string; - dataSheetId: mongoose.Types.ObjectId; - rrNumber: string; // Registro de Rastreabilidade - batchNumber: string; // Lote - color?: string; - invoiceNumber?: string; // Nota Fiscal - receivedBy?: string; // Quem recebeu - quantity: number; - unit: string; - minStock?: number; // Estoque mínimo estipulado - expirationDate?: Date; - entryDate: Date; - notes?: string; - createdAt: Date; - updatedAt: Date; -} - -const StockItemSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - createdBy: { type: String, index: true }, - dataSheetId: { type: Schema.Types.ObjectId, ref: 'TechnicalDataSheet', required: true }, - rrNumber: { type: String, required: true }, - batchNumber: { type: String, required: true }, - color: { type: String }, - invoiceNumber: { type: String }, - receivedBy: { type: String }, - quantity: { type: Number, required: true, default: 0 }, - unit: { type: String, required: true }, - minStock: { type: Number, default: 0 }, - expirationDate: { type: Date }, - entryDate: { type: Date, default: Date.now }, - notes: { type: String } -}, { timestamps: true }); - -// Compound index to prevent duplicate RR within an organization, if desirable. -// For now, indexing RR for fast lookup. -StockItemSchema.index({ organizationId: 1, rrNumber: 1 }); - -export default mongoose.models.StockItem || mongoose.model('StockItem', StockItemSchema); diff --git a/src/server/models/StockMovement.ts b/src/server/models/StockMovement.ts deleted file mode 100644 index 3ac9d4e..0000000 --- a/src/server/models/StockMovement.ts +++ /dev/null @@ -1,34 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export type MovementType = 'ENTRY' | 'ADJUSTMENT' | 'CONSUMPTION'; - -export interface IStockMovement extends Document { - organizationId?: string; - createdBy?: string; - stockItemId: mongoose.Types.ObjectId; - movementNumber?: number; - type: MovementType; - quantity: number; // Positive for entry, negative for exit - date: Date; - responsible: string; // User who performed the action - reason?: string; // For ADJUSTMENT - requester?: string; // For CONSUMPTION - notes?: string; - createdAt: Date; -} - -const StockMovementSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - createdBy: { type: String, index: true }, - stockItemId: { type: Schema.Types.ObjectId, ref: 'StockItem', required: true }, - movementNumber: { type: Number }, - type: { type: String, enum: ['ENTRY', 'ADJUSTMENT', 'CONSUMPTION'], required: true }, - quantity: { type: Number, required: true }, - date: { type: Date, default: Date.now }, - responsible: { type: String, required: true }, - reason: { type: String }, - requester: { type: String }, - notes: { type: String } -}, { timestamps: true }); - -export default mongoose.models.StockMovement || mongoose.model('StockMovement', StockMovementSchema); diff --git a/src/server/models/StoredFile.ts b/src/server/models/StoredFile.ts deleted file mode 100644 index 32db4f1..0000000 --- a/src/server/models/StoredFile.ts +++ /dev/null @@ -1,19 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IStoredFile extends Document { - filename: string; - contentType: string; - data: Buffer; - size: number; - uploadDate: Date; -} - -const StoredFileSchema: Schema = new Schema({ - filename: { type: String, required: true }, - contentType: { type: String, required: true }, - data: { type: Buffer, required: true }, - size: { type: Number, required: true }, - uploadDate: { type: Date, default: Date.now } -}); - -export default mongoose.models.StoredFile || mongoose.model('StoredFile', StoredFileSchema); diff --git a/src/server/models/SystemSettings.ts b/src/server/models/SystemSettings.ts deleted file mode 100644 index e13bec0..0000000 --- a/src/server/models/SystemSettings.ts +++ /dev/null @@ -1,19 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface ISystemSettings extends Document { - settingsId: string; - appName: string; - appSubtitle: string; - appLogoUrl?: string; - updatedBy?: string; -} - -const SystemSettingsSchema: Schema = new Schema({ - settingsId: { type: String, required: true, unique: true, default: 'global' }, - appName: { type: String, required: true, default: 'GPI' }, - appSubtitle: { type: String, required: true, default: 'Gestão de Pintura Industrial' }, - appLogoUrl: { type: String }, - updatedBy: { type: String } // Email of the dev who updated it -}, { timestamps: true }); - -export default mongoose.models.SystemSettings || mongoose.model('SystemSettings', SystemSettingsSchema); diff --git a/src/server/models/TechnicalDataSheet.ts b/src/server/models/TechnicalDataSheet.ts deleted file mode 100644 index dcd5918..0000000 --- a/src/server/models/TechnicalDataSheet.ts +++ /dev/null @@ -1,59 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface ITechnicalDataSheet extends Document { - name: string; - manufacturer?: string; - type?: string; - fileId?: mongoose.Types.ObjectId; - fileUrl: string; - uploadDate: Date; - solidsVolume?: number; - density?: number; - mixingRatio?: string; - mixingRatioWeight?: string; - mixingRatioVolume?: string; - wftMin?: number; - wftMax?: number; - dftMin?: number; - dftMax?: number; - reducer?: string; - yieldTheoretical?: number; - dftReference?: number; - yieldFactor?: number; - dilution?: number; - notes?: string; - organizationId?: string; - manufacturerCode?: string; - minStock?: number; - typicalApplication?: string; -} - -const TechnicalDataSheetSchema: Schema = new Schema({ - organizationId: { type: String, index: true }, - name: { type: String, required: true }, - manufacturer: { type: String }, - manufacturerCode: { type: String }, - type: { type: String }, - minStock: { type: Number }, - typicalApplication: { type: String }, - fileId: { type: Schema.Types.ObjectId, ref: 'StoredFile' }, - fileUrl: { type: String }, - uploadDate: { type: Date, default: Date.now }, - solidsVolume: { type: Number }, - density: { type: Number }, - mixingRatio: { type: String }, - mixingRatioWeight: { type: String }, - mixingRatioVolume: { type: String }, - wftMin: { type: Number }, - wftMax: { type: Number }, - dftMin: { type: Number }, - dftMax: { type: Number }, - reducer: { type: String }, - yieldTheoretical: { type: Number }, - dftReference: { type: Number }, - yieldFactor: { type: Number }, - dilution: { type: Number }, - notes: { type: String }, -}, { timestamps: true }); - -export default mongoose.models.TechnicalDataSheet || mongoose.model('TechnicalDataSheet', TechnicalDataSheetSchema); diff --git a/src/server/models/User.ts b/src/server/models/User.ts deleted file mode 100644 index 754b5f8..0000000 --- a/src/server/models/User.ts +++ /dev/null @@ -1,63 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export type UserRole = 'guest' | 'user' | 'admin'; - -export interface IUser extends Document { - clerkId?: string; - logtoId?: string; - email: string; - name: string; - role: UserRole; - isBanned: boolean; - organizationId?: string; - createdAt: Date; - updatedAt: Date; - lastSeenAt?: Date; -} - -const UserSchema: Schema = new Schema({ - clerkId: { - type: String, - required: false, - unique: true, - sparse: true, - index: true - }, - logtoId: { - type: String, - required: false, - unique: true, - sparse: true, - index: true - }, - organizationId: { - type: String, - index: true - }, - email: { - type: String, - required: true, - unique: true - }, - name: { - type: String, - required: true - }, - role: { - type: String, - enum: ['guest', 'user', 'admin'], - default: 'guest' - }, - isBanned: { - type: Boolean, - default: false - }, - lastSeenAt: { - type: Date, - default: Date.now - } -}, { - timestamps: true -}); - -export default mongoose.models.User || mongoose.model('User', UserSchema); diff --git a/src/server/models/YieldStudy.ts b/src/server/models/YieldStudy.ts deleted file mode 100644 index eb4cafe..0000000 --- a/src/server/models/YieldStudy.ts +++ /dev/null @@ -1,53 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; - -export interface IPieceCategory { - id: string; // Keep as string for internal mapping if needed, or convert to Sub-document - name: string; - organizationId?: string; - weight: number; - area?: number; // Área em m² para cálculo alternativo - historicalYield: number; - historicalDft: number; - efficiency: number; -} - -const PieceCategorySchema: Schema = new Schema({ - name: { type: String, required: true }, - weight: { type: Number, required: true }, - area: { type: Number }, // Área em m² (opcional) - historicalYield: { type: Number, required: true }, - historicalDft: { type: Number, required: true }, - efficiency: { type: Number, required: true }, -}); - -export interface IYieldStudy extends Document { - name: string; - organizationId?: string; - dataSheetId: mongoose.Types.ObjectId; - targetDft: number; - dilutionPercent: number; - categories: IPieceCategory[]; - totalWeight: number; - estimatedPaintVolume: number; - estimatedReducerVolume: number; - estimatedPaintVolumeByArea?: number; // Cálculo por área (m²) - estimatedReducerVolumeByArea?: number; // Cálculo por área (m²) - averageComplexity: number; -} - -const YieldStudySchema: Schema = new Schema({ - name: { type: String, required: true }, - organizationId: { type: String, index: true }, - dataSheetId: { type: Schema.Types.ObjectId, ref: 'TechnicalDataSheet', required: true }, - targetDft: { type: Number, required: true }, - dilutionPercent: { type: Number, default: 0 }, - categories: [PieceCategorySchema], - totalWeight: { type: Number }, - estimatedPaintVolume: { type: Number }, - estimatedReducerVolume: { type: Number }, - estimatedPaintVolumeByArea: { type: Number }, // Cálculo por área - estimatedReducerVolumeByArea: { type: Number }, // Cálculo por área - averageComplexity: { type: Number }, -}, { timestamps: true }); - -export default mongoose.models.YieldStudy || mongoose.model('YieldStudy', YieldStudySchema);