mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 22:14:08 +01:00
235 lines
8.4 KiB
Go
235 lines
8.4 KiB
Go
// backend/internal/repository/postgres_farm_analytics.go
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/forfarm/backend/internal/domain"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type PostgresFarmAnalyticsRepository struct {
|
|
pool *pgxpool.Pool
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewPostgresFarmAnalyticsRepository(pool *pgxpool.Pool, logger *slog.Logger) domain.AnalyticsRepository {
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
return &PostgresFarmAnalyticsRepository{
|
|
pool: pool,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) GetFarmAnalytics(ctx context.Context, farmID string) (*domain.FarmAnalytics, error) {
|
|
query := `
|
|
SELECT
|
|
farm_id, farm_name, owner_id, farm_type, total_size, latitude, longitude,
|
|
weather_temp_celsius, weather_humidity, weather_description, weather_icon,
|
|
weather_wind_speed, weather_rain_1h, weather_observed_at, weather_last_updated,
|
|
inventory_total_items, inventory_low_stock_count, inventory_last_updated,
|
|
crop_total_count, crop_growing_count, crop_last_updated,
|
|
overall_status, analytics_last_updated
|
|
FROM public.farm_analytics
|
|
WHERE farm_id = $1`
|
|
|
|
var fa domain.FarmAnalytics
|
|
// Pointers for nullable database columns
|
|
var weatherTemp, weatherHumid, weatherWind, weatherRain *float64
|
|
var weatherDesc, weatherIcon, overallStatus *string
|
|
var weatherObservedAt, weatherLastUpdated, invLastUpdated, cropLastUpdated *time.Time
|
|
|
|
err := r.pool.QueryRow(ctx, query, farmID).Scan(
|
|
&fa.FarmID, &fa.FarmName, &fa.OwnerID, &fa.FarmType, &fa.TotalSize, &fa.Latitude, &fa.Longitude,
|
|
&weatherTemp, &weatherHumid, &weatherDesc, &weatherIcon,
|
|
&weatherWind, &weatherRain, &weatherObservedAt, &weatherLastUpdated,
|
|
&fa.InventoryInfo.TotalItems, &fa.InventoryInfo.LowStockCount, &invLastUpdated,
|
|
&fa.CropInfo.TotalCount, &fa.CropInfo.GrowingCount, &cropLastUpdated,
|
|
&overallStatus, &fa.AnalyticsLastUpdated,
|
|
)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, domain.ErrNotFound // Use domain error
|
|
}
|
|
r.logger.Error("Error fetching farm analytics", "farm_id", farmID, "error", err)
|
|
return nil, fmt.Errorf("database error fetching analytics for farm %s: %w", farmID, err)
|
|
}
|
|
|
|
fa.Weather = &domain.WeatherData{
|
|
TempCelsius: weatherTemp,
|
|
Humidity: weatherHumid,
|
|
Description: weatherDesc,
|
|
Icon: weatherIcon,
|
|
WindSpeed: weatherWind,
|
|
RainVolume1h: weatherRain,
|
|
ObservedAt: weatherObservedAt,
|
|
WeatherLastUpdated: weatherLastUpdated,
|
|
}
|
|
fa.InventoryInfo.LastUpdated = invLastUpdated
|
|
fa.CropInfo.LastUpdated = cropLastUpdated
|
|
fa.OverallStatus = overallStatus
|
|
|
|
return &fa, nil
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) CreateOrUpdateFarmBaseData(ctx context.Context, farm *domain.Farm) error {
|
|
query := `
|
|
INSERT INTO public.farm_analytics (
|
|
farm_id, farm_name, owner_id, farm_type, total_size, latitude, longitude, analytics_last_updated
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
|
ON CONFLICT (farm_id) DO UPDATE SET
|
|
farm_name = EXCLUDED.farm_name,
|
|
owner_id = EXCLUDED.owner_id,
|
|
farm_type = EXCLUDED.farm_type,
|
|
total_size = EXCLUDED.total_size,
|
|
latitude = EXCLUDED.latitude,
|
|
longitude = EXCLUDED.longitude,
|
|
analytics_last_updated = NOW()`
|
|
|
|
_, err := r.pool.Exec(ctx, query,
|
|
farm.UUID, farm.Name, farm.OwnerID, farm.FarmType, farm.TotalSize, farm.Lat, farm.Lon,
|
|
)
|
|
if err != nil {
|
|
r.logger.Error("Error upserting farm base analytics", "farm_id", farm.UUID, "error", err)
|
|
return fmt.Errorf("failed to save base farm analytics for %s: %w", farm.UUID, err)
|
|
}
|
|
r.logger.Debug("Upserted farm base analytics", "farm_id", farm.UUID)
|
|
return nil
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) UpdateFarmAnalyticsWeather(ctx context.Context, farmID string, weatherData *domain.WeatherData) error {
|
|
if weatherData == nil {
|
|
return errors.New("weather data cannot be nil for update")
|
|
}
|
|
query := `
|
|
UPDATE public.farm_analytics SET
|
|
weather_temp_celsius = $2,
|
|
weather_humidity = $3,
|
|
weather_description = $4,
|
|
weather_icon = $5,
|
|
weather_wind_speed = $6,
|
|
weather_rain_1h = $7,
|
|
weather_observed_at = $8,
|
|
weather_last_updated = NOW(), -- Use current time for the update time
|
|
analytics_last_updated = NOW()
|
|
WHERE farm_id = $1`
|
|
|
|
_, err := r.pool.Exec(ctx, query,
|
|
farmID,
|
|
weatherData.TempCelsius,
|
|
weatherData.Humidity,
|
|
weatherData.Description,
|
|
weatherData.Icon,
|
|
weatherData.WindSpeed,
|
|
weatherData.RainVolume1h,
|
|
weatherData.ObservedAt,
|
|
)
|
|
if err != nil {
|
|
r.logger.Error("Error updating farm weather analytics", "farm_id", farmID, "error", err)
|
|
return fmt.Errorf("failed to update weather analytics for farm %s: %w", farmID, err)
|
|
}
|
|
r.logger.Debug("Updated farm weather analytics", "farm_id", farmID)
|
|
return nil
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) UpdateFarmAnalyticsCropStats(ctx context.Context, farmID string) error {
|
|
countQuery := `
|
|
SELECT
|
|
COUNT(*),
|
|
COUNT(*) FILTER (WHERE lower(status) = 'growing')
|
|
FROM public.croplands
|
|
WHERE farm_id = $1
|
|
`
|
|
var totalCount, growingCount int
|
|
err := r.pool.QueryRow(ctx, countQuery, farmID).Scan(&totalCount, &growingCount)
|
|
if err != nil {
|
|
if !errors.Is(err, pgx.ErrNoRows) {
|
|
r.logger.Error("Error calculating crop counts", "farm_id", farmID, "error", err)
|
|
return fmt.Errorf("failed to calculate crop stats for farm %s: %w", farmID, err)
|
|
}
|
|
}
|
|
|
|
updateQuery := `
|
|
UPDATE public.farm_analytics SET
|
|
crop_total_count = $2,
|
|
crop_growing_count = $3,
|
|
crop_last_updated = NOW(),
|
|
analytics_last_updated = NOW()
|
|
WHERE farm_id = $1`
|
|
|
|
cmdTag, err := r.pool.Exec(ctx, updateQuery, farmID, totalCount, growingCount)
|
|
if err != nil {
|
|
r.logger.Error("Error updating farm crop stats", "farm_id", farmID, "error", err)
|
|
return fmt.Errorf("failed to update crop stats for farm %s: %w", farmID, err)
|
|
}
|
|
if cmdTag.RowsAffected() == 0 {
|
|
r.logger.Warn("No farm analytics record found to update crop stats", "farm_id", farmID)
|
|
// Optionally, create the base record here if it should always exist
|
|
// return r.CreateOrUpdateFarmBaseData(ctx, &domain.Farm{UUID: farmID /* Fetch other details */})
|
|
}
|
|
|
|
r.logger.Debug("Updated farm crop stats", "farm_id", farmID, "total", totalCount, "growing", growingCount)
|
|
return nil
|
|
}
|
|
|
|
// TODO: Implement actual count calculation if needed later.
|
|
func (r *PostgresFarmAnalyticsRepository) UpdateFarmAnalyticsInventoryStats(ctx context.Context, farmID string) error {
|
|
query := `
|
|
UPDATE public.farm_analytics SET
|
|
-- inventory_total_items = (SELECT COUNT(*) FROM ... WHERE farm_id = $1), -- Example future logic
|
|
-- inventory_low_stock_count = (SELECT COUNT(*) FROM ... WHERE farm_id = $1 AND status = 'low'), -- Example
|
|
inventory_last_updated = NOW(),
|
|
analytics_last_updated = NOW()
|
|
WHERE farm_id = $1`
|
|
|
|
cmdTag, err := r.pool.Exec(ctx, query, farmID)
|
|
if err != nil {
|
|
r.logger.Error("Error touching inventory timestamp in farm analytics", "farm_id", farmID, "error", err)
|
|
return fmt.Errorf("failed to update inventory stats timestamp for farm %s: %w", farmID, err)
|
|
}
|
|
if cmdTag.RowsAffected() == 0 {
|
|
r.logger.Warn("No farm analytics record found to update inventory timestamp", "farm_id", farmID)
|
|
}
|
|
|
|
r.logger.Debug("Updated farm inventory timestamp", "farm_id", farmID)
|
|
return nil
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) DeleteFarmAnalytics(ctx context.Context, farmID string) error {
|
|
query := `DELETE FROM public.farm_analytics WHERE farm_id = $1`
|
|
_, err := r.pool.Exec(ctx, query, farmID)
|
|
if err != nil {
|
|
r.logger.Error("Error deleting farm analytics", "farm_id", farmID, "error", err)
|
|
return fmt.Errorf("failed to delete analytics for farm %s: %w", farmID, err)
|
|
}
|
|
r.logger.Debug("Deleted farm analytics", "farm_id", farmID)
|
|
return nil
|
|
}
|
|
|
|
func (r *PostgresFarmAnalyticsRepository) UpdateFarmOverallStatus(ctx context.Context, farmID string, status string) error {
|
|
query := `
|
|
UPDATE public.farm_analytics SET
|
|
overall_status = $2,
|
|
analytics_last_updated = NOW()
|
|
WHERE farm_id = $1`
|
|
|
|
cmdTag, err := r.pool.Exec(ctx, query, farmID, status)
|
|
if err != nil {
|
|
r.logger.Error("Error updating farm overall status", "farm_id", farmID, "status", status, "error", err)
|
|
return fmt.Errorf("failed to update overall status for farm %s: %w", farmID, err)
|
|
}
|
|
if cmdTag.RowsAffected() == 0 {
|
|
r.logger.Warn("No farm analytics record found to update overall status", "farm_id", farmID)
|
|
}
|
|
r.logger.Debug("Updated farm overall status", "farm_id", farmID, "status", status)
|
|
return nil
|
|
}
|