diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1af5fd3..187b1e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -135,7 +135,7 @@ verify: - | HEALTH_OK=0 for i in 1 2 3 4 5 6; do - if docker compose -f $DOCKER_COMPOSE_FILE exec -T mvp-backend curl -sf http://localhost:3001/health > /dev/null 2>&1; then + if node -e "require('http').get('http://localhost:3001/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))" > /dev/null 2>&1; then echo "OK: Backend health check passed" HEALTH_OK=1 break diff --git a/backend/package.json b/backend/package.json index 3551ba0..792b92a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ "test": "jest", "test:watch": "jest --watch", "test:feature": "jest --testPathPattern=src/features/${npm_config_feature}", + "migrate": "npm run migrate:all", "migrate:all": "ts-node src/_system/migrations/run-all.ts", "migrate:feature": "ts-node src/_system/migrations/run-feature.ts", "schema:generate": "ts-node src/_system/schema/generate.ts", diff --git a/data/vehicle-etl/source-makes.txt b/data/vehicle-etl/source-makes.txt deleted file mode 100644 index 135dd26..0000000 --- a/data/vehicle-etl/source-makes.txt +++ /dev/null @@ -1,53 +0,0 @@ -acura -alfa_romeo -aston_martin -audi -bentley -bmw -buick -cadillac -chevrolet -chrysler -dodge -ferrari -fiat -ford -genesis -gmc -honda -hummer -hyundai -infiniti -isuzu -jaguar -jeep -kia -lamborghini -land_rover -lexus -lincoln -lotus -lucid -maserati -mazda -mclaren -mercury -mini -mitsubishi -nissan -oldsmobile -plymouth -polestar -pontiac -porsche -ram -rivian -rolls_royce -saab -scion -smart -subaru -tesla -toyota -volkswagen -volvo diff --git a/docs/PROMPTS.md b/docs/PROMPTS.md index d7e26e5..e4c80d0 100644 --- a/docs/PROMPTS.md +++ b/docs/PROMPTS.md @@ -20,12 +20,11 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen *** ROLE *** -You are a senior DevOps SRE in charge of improving the deployment of htis app. +You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet management application. *** ACTION *** -- You need to add a deployment step where the vehicle-etl process that's currentl configured to be manual, is executed during the gitlab deployment. -- Research the directory @data/vehicle-etl/ to understand the process. -- The source database will be @data/vehicle-etl/snapshots/vehicle-drop-down.sqlite +- There are errors in deployment then also console errors +- It appears when trying to run the npm migrations there is an error. - Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change. *** CONTEXT *** @@ -33,3 +32,135 @@ You are a senior DevOps SRE in charge of improving the deployment of htis app. *** CHANGES TO IMPLEMENT *** - Plan and recommend the best solution for this change + +Here are the relevant logs. +egullickson@mvp-runner-1:~/motovaultpro$ docker compose run --rm mvp-backend npm run migrate +[+] 3/3t 3/31 + ✔ Network motovaultpro_backend Created 0.1s + ✔ Container mvp-postgres Running 0.0s + ✔ Container mvp-redis Running 0.0s +Image motovaultpro-mvp-backend Building +[+] Building 196.2s (26/26) FINISHED + => [internal] load local bake definitions 0.0s + => => reading from stdin 622B +=> => unpacking to docker.io/library/motovaultpro-mvp-backend:latest 9.5s + => resolving provenance for metadata file 0.0s +Image motovaultpro-mvp-backend Built +Container motovaultpro-mvp-backend-run-9a43629523b7 Creating +Container motovaultpro-mvp-backend-run-9a43629523b7 Created +npm error Missing script: "migrate" +npm error +npm error To see a list of scripts, run: +npm error npm run +npm error A complete log of this run can be found in: /home/nodejs/.npm/_logs/2025-12-20T16_15_34_540Z-debug-0.log +index-BZX6X6vG.js:21 [Auth Gate] Module loaded, authInitialized: false +index-BZX6X6vG.js:21 [Navigation] State rehydrated successfully +index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object +index-BZX6X6vG.js:21 [TokenInjector] Component loaded +index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: true isAuthenticated: false isAuthGateReady: false +index-BZX6X6vG.js:27 MotoVaultPro status: Object +index-BZX6X6vG.js:21 [useIsAuthInitialized] Starting poll for auth init +index-BZX6X6vG.js:27 DataSync: Skipping prefetch - user not authenticated +index-BZX6X6vG.js:27 Window width: 1728 User agent mobile: false Mobile mode: false +index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: true, Authenticated: false, User: null +index-BZX6X6vG.js:21 [Auth Debug] State check: Object +index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: false (was: false ) +index-BZX6X6vG.js:21 [IndexedDB] Loaded 0 items into cache +index-BZX6X6vG.js:21 [IndexedDB] Storage initialized successfully +index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #1: initialized=false +index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #2: initialized=false +index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #3: initialized=false +index-BZX6X6vG.js:21 [Auth0Provider] Redirect callback triggered Object +index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object +index-BZX6X6vG.js:21 [TokenInjector] Component loaded +index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false +index-BZX6X6vG.js:27 MotoVaultPro status: Object +index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state +index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: false, Authenticated: true, User: present +index-BZX6X6vG.js:21 [Auth Debug] State check: Object +index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false +index-BZX6X6vG.js:27 MotoVaultPro status: Object +index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state +index-BZX6X6vG.js:21 [Auth] IndexedDB storage is ready +index-BZX6X6vG.js:21 [Mobile Auth] Initializing token cache (mobile: false, delay: 100ms) +index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #4: initialized=false +index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object +index-BZX6X6vG.js:21 [Mobile Auth] Token pre-warming successful +index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: true (was: false ) +index-BZX6X6vG.js:21 [DEBUG Auth Gate] Authentication fully initialized +index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #5: initialized=true +index-BZX6X6vG.js:21 [useIsAuthInitialized] Auth initialized via poll! +index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true +index-BZX6X6vG.js:27 MotoVaultPro status: Object +index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object +index-BZX6X6vG.js:21 [TokenInjector] Component loaded +index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true +index-BZX6X6vG.js:27 MotoVaultPro status: Object +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Query function called - fetching vehicles +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false +index-BZX6X6vG.js:21 Uncaught Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. + at yi (index-BZX6X6vG.js:21:32113) + at hi (index-BZX6X6vG.js:21:31638) + at Yc (index-BZX6X6vG.js:21:64112) + at Hc (index-BZX6X6vG.js:21:63724) + at index-BZX6X6vG.js:27:314956 + at Zu (index-BZX6X6vG.js:21:97033) + at Ad (index-BZX6X6vG.js:21:113396) + at Fd (index-BZX6X6vG.js:21:113280) + at Ad (index-BZX6X6vG.js:21:113441) + at Fd (index-BZX6X6vG.js:21:113280) diff --git a/frontend/src/features/vehicles/hooks/useOptimisticVehicles.ts b/frontend/src/features/vehicles/hooks/useOptimisticVehicles.ts index c2b3df7..a6039f5 100644 --- a/frontend/src/features/vehicles/hooks/useOptimisticVehicles.ts +++ b/frontend/src/features/vehicles/hooks/useOptimisticVehicles.ts @@ -40,9 +40,11 @@ export function useOptimisticVehicles(vehicles: Vehicle[] = []) { }; // Sync optimistic state with actual vehicles when they change + // Guard: Skip if both are empty arrays (prevents infinite loop on reference changes) useEffect(() => { + if (vehicles.length === 0 && optimisticVehicles.length === 0) return; setOptimisticVehicles(vehicles); - }, [vehicles]); + }, [vehicles, optimisticVehicles.length]); const optimisticCreateVehicle = async (data: CreateVehicleRequest) => { // Create optimistic vehicle with temporary ID diff --git a/frontend/src/features/vehicles/hooks/useVehicleTransitions.ts b/frontend/src/features/vehicles/hooks/useVehicleTransitions.ts index edd2419..501665b 100644 --- a/frontend/src/features/vehicles/hooks/useVehicleTransitions.ts +++ b/frontend/src/features/vehicles/hooks/useVehicleTransitions.ts @@ -3,7 +3,7 @@ * @ai-context Non-blocking updates for better UI responsiveness */ -import { useTransition, useState, useCallback } from 'react'; +import { useTransition, useState, useCallback, useEffect } from 'react'; import { Vehicle } from '../types/vehicles.types'; export function useVehicleSearch(vehicles: Vehicle[]) { @@ -42,35 +42,32 @@ export function useVehicleSearch(vehicles: Vehicle[]) { }); }, [vehicles]); - // Update filtered vehicles when vehicles data changes - const updateVehicles = useCallback((newVehicles: Vehicle[]) => { - startTransition(() => { - if (!searchQuery.trim()) { - setFilteredVehicles(newVehicles); - } else { - // Re-apply search filter to new data - const filtered = newVehicles.filter(vehicle => { - const searchTerm = searchQuery.toLowerCase(); - return ( - vehicle.nickname?.toLowerCase().includes(searchTerm) || - vehicle.make?.toLowerCase().includes(searchTerm) || - vehicle.model?.toLowerCase().includes(searchTerm) || - vehicle.vin.toLowerCase().includes(searchTerm) || - vehicle.year?.toString().includes(searchTerm) - ); - }); - setFilteredVehicles(filtered); - } - }); - }, [searchQuery]); + // Auto-sync filtered vehicles when source vehicles change + // Note: Direct state update (no startTransition) to avoid deferred render cascades + useEffect(() => { + if (!searchQuery.trim()) { + setFilteredVehicles(vehicles); + } else { + const filtered = vehicles.filter(vehicle => { + const searchTerm = searchQuery.toLowerCase(); + return ( + vehicle.nickname?.toLowerCase().includes(searchTerm) || + vehicle.make?.toLowerCase().includes(searchTerm) || + vehicle.model?.toLowerCase().includes(searchTerm) || + vehicle.vin.toLowerCase().includes(searchTerm) || + vehicle.year?.toString().includes(searchTerm) + ); + }); + setFilteredVehicles(filtered); + } + }, [vehicles, searchQuery]); return { searchQuery, filteredVehicles, isPending, handleSearch, - clearSearch, - updateVehicles + clearSearch }; } diff --git a/frontend/src/features/vehicles/mobile/VehiclesMobileScreen.tsx b/frontend/src/features/vehicles/mobile/VehiclesMobileScreen.tsx index cf09c15..00a7c38 100644 --- a/frontend/src/features/vehicles/mobile/VehiclesMobileScreen.tsx +++ b/frontend/src/features/vehicles/mobile/VehiclesMobileScreen.tsx @@ -3,7 +3,7 @@ * @ai-context Enhanced with Suspense, optimistic updates, and transitions */ -import React, { useTransition, useEffect } from 'react'; +import React, { useTransition, useMemo } from 'react'; import { Box, Typography, Grid, Button } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import { useVehicles } from '../hooks/useVehicles'; @@ -40,24 +40,22 @@ export const VehiclesMobileScreen: React.FC = ({ }) => { const { data: vehicles, isLoading } = useVehicles(); const [_isPending, startTransition] = useTransition(); - + + // Stable reference for empty array (prevents infinite loop when vehicles is undefined) + const safeVehicles = useMemo( + () => (Array.isArray(vehicles) ? vehicles : []), + [vehicles] + ); + // React 19 optimistic updates const { optimisticVehicles, isPending: isOptimisticPending - } = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []); - - // Enhanced search with transitions - const { - filteredVehicles, - updateVehicles - } = useVehicleSearch(optimisticVehicles); - - // Update search when optimistic vehicles change - useEffect(() => { - updateVehicles(optimisticVehicles); - }, [optimisticVehicles, updateVehicles]); + } = useOptimisticVehicles(safeVehicles); + // Enhanced search with transitions (auto-syncs when vehicles change) + const { filteredVehicles } = useVehicleSearch(optimisticVehicles); + const handleVehicleSelect = (vehicle: Vehicle) => { // Use transition to avoid blocking UI during navigation startTransition(() => { diff --git a/frontend/src/features/vehicles/pages/VehiclesPage.tsx b/frontend/src/features/vehicles/pages/VehiclesPage.tsx index c89a569..10ac5e8 100644 --- a/frontend/src/features/vehicles/pages/VehiclesPage.tsx +++ b/frontend/src/features/vehicles/pages/VehiclesPage.tsx @@ -3,7 +3,7 @@ * @ai-context Enhanced with Suspense, useOptimistic, and useTransition */ -import React, { useState, useEffect, useTransition } from 'react'; +import React, { useState, useTransition, useMemo } from 'react'; import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import SearchIcon from '@mui/icons-material/Search'; @@ -25,33 +25,33 @@ export const VehiclesPage: React.FC = () => { const queryClient = useQueryClient(); const { data: vehicles, isLoading } = useVehicles(); const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle); - + + // Stable reference for empty array (prevents infinite loop when vehicles is undefined) + const safeVehicles = useMemo( + () => (Array.isArray(vehicles) ? vehicles : []), + [vehicles] + ); + // React 19 optimistic updates and transitions - const { - optimisticVehicles, - isPending: isOptimisticPending, - optimisticCreateVehicle, - optimisticDeleteVehicle - } = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []); + const { + optimisticVehicles, + isPending: isOptimisticPending, + optimisticCreateVehicle, + optimisticDeleteVehicle + } = useOptimisticVehicles(safeVehicles); - const { - searchQuery, - filteredVehicles, - isPending: isSearchPending, - handleSearch, - clearSearch, - updateVehicles + const { + searchQuery, + filteredVehicles, + isPending: isSearchPending, + handleSearch, + clearSearch } = useVehicleSearch(optimisticVehicles); const [isPending, startTransition] = useTransition(); const [showForm, setShowForm] = useState(false); const [stagedImageFile, setStagedImageFile] = useState(null); - // Update search vehicles when optimistic vehicles change - useEffect(() => { - updateVehicles(optimisticVehicles); - }, [optimisticVehicles, updateVehicles]); - const handleSelectVehicle = (id: string) => { // Use transition for navigation to avoid blocking UI startTransition(() => {