diff --git a/.env b/.env new file mode 100644 index 0000000..9d931f4 --- /dev/null +++ b/.env @@ -0,0 +1,37 @@ +# TurfTracker Environment Configuration +# Copy this file to .env and fill in your values + +# Database Configuration +# You can use either individual fields OR a full DATABASE_URL (DATABASE_URL takes precedence) +DB_HOST=db +DB_PORT=5432 +DB_NAME=turftracker +DB_USER=turftracker +DB_PASSWORD=password123 +# DATABASE_URL=postgresql://turftracker:password123@db:5432/turftracker + +# JWT Secret - REQUIRED: Used for signing authentication tokens +# Generate a secure random string (see README for generation commands) +# NEVER use the default value in production! +JWT_SECRET=your-super-secret-jwt-key-change-this-in-production + +# Authentik OAuth2 Configuration (Optional) +# Configure these to enable SSO login through your Authentik instance +AUTHENTIK_CLIENT_ID=your-authentik-client-id +AUTHENTIK_CLIENT_SECRET=your-authentik-client-secret +AUTHENTIK_BASE_URL=https://your-authentik-domain.com +AUTHENTIK_CALLBACK_URL=https://turftracker.kaspers.us/api/auth/authentik/callback + +# Weather API Configuration +# Get a free API key from https://openweathermap.org/api +WEATHER_API_KEY=5bae1b310158565c65f982d4074e803b + +# Application URLs (automatically configured for production) +FRONTEND_URL=https://turftracker.kaspers.us + +# Node Environment +NODE_ENV=development + +# Port Configuration (optional - defaults are set) +PORT=5000 +FRONTEND_PORT=3000 \ No newline at end of file diff --git a/database/migrations/add_unique_constraint_weather_data.sql b/database/migrations/add_unique_constraint_weather_data.sql new file mode 100644 index 0000000..0a9947c --- /dev/null +++ b/database/migrations/add_unique_constraint_weather_data.sql @@ -0,0 +1,18 @@ +-- Ensure weather_data upsert works by providing a unique index +-- Safe to run multiple times +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_indexes + WHERE schemaname = 'public' AND indexname = 'idx_weather_data_property_date' + ) THEN + DROP INDEX IF EXISTS idx_weather_data_property_date; + END IF; +EXCEPTION WHEN undefined_table THEN + -- table may not exist yet in some environments; ignore + NULL; +END $$; + +CREATE UNIQUE INDEX IF NOT EXISTS ux_weather_data_property_date + ON weather_data(property_id, date); + diff --git a/frontend/src/components/Applications/ApplicationViewModal.js b/frontend/src/components/Applications/ApplicationViewModal.js index 545fc86..b5ad223 100644 --- a/frontend/src/components/Applications/ApplicationViewModal.js +++ b/frontend/src/components/Applications/ApplicationViewModal.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import * as turf from '@turf/turf'; -import { applicationsAPI } from '../../services/api'; +import { applicationsAPI, weatherAPI } from '../../services/api'; import PropertyMap from '../Maps/PropertyMap'; import { XMarkIcon, ClockIcon, MapPinIcon, WrenchScrewdriverIcon, BeakerIcon } from '@heroicons/react/24/outline'; @@ -9,6 +9,8 @@ const ApplicationViewModal = ({ application, propertyDetails, onClose }) => { const [sections, setSections] = useState([]); const [planDetails, setPlanDetails] = useState(null); const [loading, setLoading] = useState(true); + const [weather, setWeather] = useState(null); + const [weatherError, setWeatherError] = useState(null); // Haversine distance between two lat/lng in meters const haversineMeters = (lat1, lng1, lat2, lng2) => { @@ -135,6 +137,19 @@ const ApplicationViewModal = ({ application, propertyDetails, onClose }) => { console.log('No application logs found:', error); } + // Fetch current weather for this plan's property + try { + const propId = fetchedPlanDetails.property_id || application.propertyId; + if (propId) { + const wx = await weatherAPI.getCurrent(propId); + setWeather(wx.data.data.weather); + setWeatherError(null); + } + } catch (e) { + console.warn('Weather fetch failed:', e?.response?.data || e.message); + setWeatherError('Weather unavailable'); + } + } catch (error) { console.error('Failed to fetch application data:', error); } finally { @@ -242,6 +257,29 @@ const ApplicationViewModal = ({ application, propertyDetails, onClose }) => { + {/* Weather */} +
Weather information coming soon...
-