mirror of
https://github.com/sstent/aicyclingcoach-go.git
synced 2026-06-06 02:44:00 +00:00
change to TUI
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
# Multi-stage build for container-first development
|
||||
FROM python:3.11-slim-bullseye AS builder
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# Install system dependencies for building
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends gcc libpq-dev && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Runtime stage
|
||||
FROM python:3.11-slim-bullseye AS runtime
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# Install runtime system dependencies only
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends libpq5 && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy installed packages from builder stage
|
||||
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
||||
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create entrypoint script for migration handling
|
||||
RUN echo '#!/bin/bash\n\
|
||||
set -e\n\
|
||||
\n\
|
||||
# Run database migrations synchronously\n\
|
||||
echo "Running database migrations..."\n\
|
||||
python -m alembic upgrade head\n\
|
||||
\n\
|
||||
# Verify migration success\n\
|
||||
echo "Verifying migration status..."\n\
|
||||
python -m alembic current\n\
|
||||
\n\
|
||||
# Start the application\n\
|
||||
echo "Starting application..."\n\
|
||||
exec "$@"' > /app/entrypoint.sh && \
|
||||
chmod +x /app/entrypoint.sh
|
||||
|
||||
# Create non-root user and logs directory
|
||||
RUN useradd -m appuser && \
|
||||
mkdir -p /app/logs && \
|
||||
chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose application port
|
||||
EXPOSE 8000
|
||||
|
||||
# Use entrypoint for migration automation
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[alembic]
|
||||
script_location = alembic
|
||||
sqlalchemy.url = postgresql+asyncpg://postgres:password@db:5432/cycling
|
||||
sqlalchemy.url = sqlite+aiosqlite:///data/cycling_coach.db
|
||||
|
||||
[loggers]
|
||||
keys = root
|
||||
|
||||
+28
-15
@@ -1,16 +1,21 @@
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
||||
from alembic import context
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add app directory to path
|
||||
sys.path.append(os.getcwd())
|
||||
# Add backend directory to path
|
||||
backend_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
# Import base and models
|
||||
from app.models.base import Base
|
||||
from app.config import settings
|
||||
from backend.app.models.base import Base
|
||||
from backend.app.config import settings
|
||||
|
||||
# Import all models to ensure they're registered
|
||||
from backend.app.models import *
|
||||
|
||||
config = context.config
|
||||
fileConfig(config.config_file_name)
|
||||
@@ -19,12 +24,13 @@ target_metadata = Base.metadata
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode."""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
url = settings.DATABASE_URL
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
render_as_batch=True, # Important for SQLite
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
@@ -32,21 +38,28 @@ def run_migrations_offline():
|
||||
|
||||
async def run_migrations_online():
|
||||
"""Run migrations in 'online' mode."""
|
||||
connectable = AsyncEngine(
|
||||
engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
future=True,
|
||||
url=settings.DATABASE_URL,
|
||||
)
|
||||
# Ensure data directory exists
|
||||
data_dir = Path("data")
|
||||
data_dir.mkdir(exist_ok=True)
|
||||
|
||||
connectable = create_async_engine(
|
||||
settings.DATABASE_URL,
|
||||
poolclass=pool.NullPool,
|
||||
connect_args={"check_same_thread": False} if "sqlite" in settings.DATABASE_URL else {}
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
def do_run_migrations(connection):
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
render_as_batch=True, # Important for SQLite ALTER TABLE support
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+14
-5
@@ -1,11 +1,20 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
class Settings(BaseSettings):
|
||||
DATABASE_URL: str
|
||||
GPX_STORAGE_PATH: str
|
||||
AI_MODEL: str = "openrouter/auto"
|
||||
API_KEY: str
|
||||
# Database settings
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///data/cycling_coach.db"
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
# File storage settings
|
||||
GPX_STORAGE_PATH: str = "data/gpx"
|
||||
|
||||
# AI settings
|
||||
AI_MODEL: str = "deepseek/deepseek-r1"
|
||||
OPENROUTER_API_KEY: str = ""
|
||||
|
||||
# Garmin settings
|
||||
GARMIN_USERNAME: str = ""
|
||||
GARMIN_PASSWORD: str = ""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
|
||||
settings = Settings()
|
||||
+27
-3
@@ -1,10 +1,19 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:password@db:5432/cycling")
|
||||
# Use SQLite database in data directory
|
||||
DATA_DIR = Path("data")
|
||||
DATABASE_PATH = DATA_DIR / "cycling_coach.db"
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DATABASE_PATH}")
|
||||
|
||||
engine = create_async_engine(
|
||||
DATABASE_URL,
|
||||
echo=False, # Set to True for SQL debugging
|
||||
connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
|
||||
)
|
||||
|
||||
engine = create_async_engine(DATABASE_URL, echo=True)
|
||||
AsyncSessionLocal = sessionmaker(
|
||||
bind=engine,
|
||||
class_=AsyncSession,
|
||||
@@ -15,4 +24,19 @@ Base = declarative_base()
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
yield session
|
||||
|
||||
async def init_db():
|
||||
"""Initialize the database by creating all tables."""
|
||||
# Ensure data directory exists
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# Import all models to ensure they are registered
|
||||
from .models import (
|
||||
user, rule, plan, plan_rule, workout,
|
||||
analysis, route, section, garmin_sync_log, prompt
|
||||
)
|
||||
|
||||
# Create all tables
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.services.ai_service import AIService
|
||||
from backend.app.database import get_db
|
||||
from backend.app.services.ai_service import AIService
|
||||
from typing import AsyncGenerator
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from .route import Route
|
||||
from .section import Section
|
||||
from .rule import Rule
|
||||
from .plan import Plan
|
||||
from .plan_rule import PlanRule
|
||||
from .plan_rule import plan_rules
|
||||
from .user import User
|
||||
from .workout import Workout
|
||||
from .analysis import Analysis
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, JSON, Boolean, DateTime, func
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, JSON, Boolean, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
from .base import BaseModel
|
||||
|
||||
@@ -13,7 +14,7 @@ class Analysis(BaseModel):
|
||||
suggestions = Column(JSON)
|
||||
approved = Column(Boolean, default=False)
|
||||
created_plan_id = Column(Integer, ForeignKey('plans.id'))
|
||||
approved_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
approved_at = Column(DateTime, default=datetime.utcnow) # Changed from server_default=func.now()
|
||||
|
||||
# Relationships
|
||||
workout = relationship("Workout", back_populates="analyses")
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
from datetime import datetime
|
||||
from uuid import UUID, uuid4
|
||||
from sqlalchemy import Column, DateTime
|
||||
from sqlalchemy import Column, Integer, DateTime
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class BaseModel(Base):
|
||||
__abstract__ = True
|
||||
|
||||
id = Column(PG_UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from sqlalchemy import Column, Integer, ForeignKey
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy import Column, Integer, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from .base import BaseModel
|
||||
|
||||
class Plan(BaseModel):
|
||||
__tablename__ = "plans"
|
||||
|
||||
jsonb_plan = Column(JSONB, nullable=False)
|
||||
jsonb_plan = Column(JSON, nullable=False) # Changed from JSONB to JSON for SQLite compatibility
|
||||
version = Column(Integer, nullable=False)
|
||||
parent_plan_id = Column(Integer, ForeignKey('plans.id'), nullable=True)
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
from sqlalchemy import Column, Integer, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from .base import BaseModel
|
||||
from sqlalchemy import Column, Integer, ForeignKey, Table
|
||||
from .base import Base
|
||||
|
||||
class PlanRule(BaseModel):
|
||||
__tablename__ = "plan_rules"
|
||||
|
||||
plan_id = Column(Integer, ForeignKey('plans.id'), primary_key=True)
|
||||
rule_id = Column(Integer, ForeignKey('rules.id'), primary_key=True)
|
||||
|
||||
plan = relationship("Plan", back_populates="rules")
|
||||
rule = relationship("Rule", back_populates="plans")
|
||||
# Association table for many-to-many relationship between plans and rules
|
||||
plan_rules = Table(
|
||||
'plan_rules', Base.metadata,
|
||||
Column('plan_id', Integer, ForeignKey('plans.id'), primary_key=True),
|
||||
Column('rule_id', Integer, ForeignKey('rules.id'), primary_key=True)
|
||||
)
|
||||
@@ -1,7 +1,12 @@
|
||||
from .base import BaseModel
|
||||
from sqlalchemy import Column, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from .base import BaseModel
|
||||
|
||||
class User(BaseModel):
|
||||
__tablename__ = "users"
|
||||
|
||||
plans = relationship("Plan", back_populates="user")
|
||||
username = Column(String(100), nullable=False, unique=True)
|
||||
email = Column(String(255), nullable=True)
|
||||
|
||||
# Note: Relationship removed as Plan model doesn't have user_id field
|
||||
# plans = relationship("Plan", back_populates="user")
|
||||
@@ -1,9 +1,9 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.models.workout import Workout
|
||||
from app.models.plan import Plan
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.workout import Workout
|
||||
from backend.app.models.plan import Plan
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog
|
||||
from sqlalchemy import select, desc
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, Query, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from app.services.export_service import ExportService
|
||||
from backend.app.services.export_service import ExportService
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from fastapi import APIRouter, Depends, BackgroundTasks
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.dependencies import verify_api_key
|
||||
from app.services.workout_sync import WorkoutSyncService
|
||||
from app.database import get_db
|
||||
from backend.app.dependencies import verify_api_key
|
||||
from backend.app.services.workout_sync import WorkoutSyncService
|
||||
from backend.app.database import get_db
|
||||
|
||||
router = APIRouter(dependencies=[Depends(verify_api_key)])
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.services.gpx import parse_gpx, store_gpx_file
|
||||
from app.schemas.gpx import RouteCreate, Route as RouteSchema
|
||||
from app.models import Route
|
||||
from backend.app.database import get_db
|
||||
from backend.app.services.gpx import parse_gpx, store_gpx_file
|
||||
from backend.app.schemas.gpx import RouteCreate, Route as RouteSchema
|
||||
from backend.app.models import Route
|
||||
import os
|
||||
|
||||
router = APIRouter(prefix="/gpx", tags=["GPX Routes"])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import PlainTextResponse, JSONResponse
|
||||
from app.services.health_monitor import HealthMonitor
|
||||
from backend.app.services.health_monitor import HealthMonitor
|
||||
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST, Gauge
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, UploadFile, File, Form, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from app.services.import_service import ImportService
|
||||
from backend.app.services.import_service import ImportService
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.database import get_db
|
||||
from app.models.plan import Plan as PlanModel
|
||||
from app.models.rule import Rule
|
||||
from app.schemas.plan import PlanCreate, Plan as PlanSchema, PlanGenerationRequest, PlanGenerationResponse
|
||||
from app.dependencies import get_ai_service
|
||||
from app.services.ai_service import AIService
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.plan import Plan as PlanModel
|
||||
from backend.app.models.rule import Rule
|
||||
from backend.app.schemas.plan import PlanCreate, Plan as PlanSchema, PlanGenerationRequest, PlanGenerationResponse
|
||||
from backend.app.dependencies import get_ai_service
|
||||
from backend.app.services.ai_service import AIService
|
||||
from uuid import UUID, uuid4
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
@@ -3,10 +3,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.prompt import Prompt
|
||||
from app.schemas.prompt import Prompt as PromptSchema, PromptCreate, PromptUpdate
|
||||
from app.services.prompt_manager import PromptManager
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.prompt import Prompt
|
||||
from backend.app.schemas.prompt import Prompt as PromptSchema, PromptCreate, PromptUpdate
|
||||
from backend.app.services.prompt_manager import PromptManager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.database import get_db
|
||||
from app.models.rule import Rule
|
||||
from app.schemas.rule import RuleCreate, Rule as RuleSchema, NaturalLanguageRuleRequest, ParsedRuleResponse
|
||||
from app.dependencies import get_ai_service
|
||||
from app.services.ai_service import AIService
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.rule import Rule
|
||||
from backend.app.schemas.rule import RuleCreate, Rule as RuleSchema, NaturalLanguageRuleRequest, ParsedRuleResponse
|
||||
from backend.app.dependencies import get_ai_service
|
||||
from backend.app.services.ai_service import AIService
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -3,17 +3,17 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.workout import Workout
|
||||
from app.models.analysis import Analysis
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from app.models.plan import Plan
|
||||
from app.schemas.workout import Workout as WorkoutSchema, WorkoutSyncStatus, WorkoutMetric
|
||||
from app.schemas.analysis import Analysis as AnalysisSchema
|
||||
from app.schemas.plan import Plan as PlanSchema
|
||||
from app.services.workout_sync import WorkoutSyncService
|
||||
from app.services.ai_service import AIService
|
||||
from app.services.plan_evolution import PlanEvolutionService
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.workout import Workout
|
||||
from backend.app.models.analysis import Analysis
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog
|
||||
from backend.app.models.plan import Plan
|
||||
from backend.app.schemas.workout import Workout as WorkoutSchema, WorkoutSyncStatus, WorkoutMetric
|
||||
from backend.app.schemas.analysis import Analysis as AnalysisSchema
|
||||
from backend.app.schemas.plan import Plan as PlanSchema
|
||||
from backend.app.services.workout_sync import WorkoutSyncService
|
||||
from backend.app.services.ai_service import AIService
|
||||
from backend.app.services.plan_evolution import PlanEvolutionService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,8 +3,8 @@ import asyncio
|
||||
from typing import Dict, Any, List, Optional
|
||||
import httpx
|
||||
import json
|
||||
from app.services.prompt_manager import PromptManager
|
||||
from app.models.workout import Workout
|
||||
from backend.app.services.prompt_manager import PromptManager
|
||||
from backend.app.models.workout import Workout
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -2,8 +2,8 @@ import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
from app.database import SessionLocal
|
||||
from app.models import Route, Rule, Plan
|
||||
from backend.app.database import SessionLocal
|
||||
from backend.app.models import Route, Rule, Plan
|
||||
import tempfile
|
||||
import logging
|
||||
import shutil
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
import logging
|
||||
from fastapi import UploadFile, HTTPException
|
||||
import gpxpy
|
||||
from app.config import settings
|
||||
from backend.app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ from datetime import datetime
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from sqlalchemy import text
|
||||
from app.database import get_db
|
||||
from app.models.garmin_sync_log import GarminSyncLog, SyncStatus
|
||||
from backend.app.database import get_db
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog, SyncStatus
|
||||
import requests
|
||||
from app.config import settings
|
||||
from backend.app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -43,12 +43,12 @@ class HealthMonitor:
|
||||
|
||||
def _get_sync_queue_size(self) -> int:
|
||||
"""Get number of pending sync operations"""
|
||||
from app.models.garmin_sync_log import GarminSyncLog, SyncStatus
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog, SyncStatus
|
||||
return GarminSyncLog.query.filter_by(status=SyncStatus.PENDING).count()
|
||||
|
||||
def _count_pending_analyses(self) -> int:
|
||||
"""Count workouts needing analysis"""
|
||||
from app.models.workout import Workout
|
||||
from backend.app.models.workout import Workout
|
||||
return Workout.query.filter_by(analysis_status='pending').count()
|
||||
|
||||
def _check_database(self) -> str:
|
||||
|
||||
@@ -3,8 +3,8 @@ import zipfile
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from app.database import SessionLocal
|
||||
from app.models import Route, Rule, Plan
|
||||
from backend.app.database import SessionLocal
|
||||
from backend.app.models import Route, Rule, Plan
|
||||
import shutil
|
||||
import logging
|
||||
from sqlalchemy import and_
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.services.ai_service import AIService
|
||||
from app.models.analysis import Analysis
|
||||
from app.models.plan import Plan
|
||||
from backend.app.services.ai_service import AIService
|
||||
from backend.app.models.analysis import Analysis
|
||||
from backend.app.models.plan import Plan
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update, func
|
||||
from app.models.prompt import Prompt
|
||||
from backend.app.models.prompt import Prompt
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, desc
|
||||
from app.services.garmin import GarminService, GarminAPIError, GarminAuthError
|
||||
from app.models.workout import Workout
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from backend.app.services.garmin import GarminService, GarminAPIError, GarminAuthError
|
||||
from backend.app.models.workout import Workout
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import Optional
|
||||
backend_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from app.database import get_database_url
|
||||
from backend.app.database import get_database_url
|
||||
|
||||
class DatabaseManager:
|
||||
"""Handles database backup and restore operations."""
|
||||
|
||||
@@ -18,7 +18,7 @@ from alembic import command
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.script import ScriptDirectory
|
||||
from sqlalchemy import create_engine, text
|
||||
from app.database import get_database_url
|
||||
from backend.app.database import get_database_url
|
||||
|
||||
class MigrationChecker:
|
||||
"""Validates migration compatibility and integrity."""
|
||||
|
||||
@@ -17,7 +17,7 @@ from alembic import command
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.script import ScriptDirectory
|
||||
import sqlalchemy as sa
|
||||
from app.database import get_database_url
|
||||
from backend.app.database import get_database_url
|
||||
|
||||
def get_alembic_config():
|
||||
"""Get Alembic configuration."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
from app.database import get_db, Base
|
||||
from backend.app.main import app
|
||||
from backend.app.database import get_db, Base
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
from app.services.ai_service import AIService, AIServiceError
|
||||
from app.models.workout import Workout
|
||||
from backend.app.services.ai_service import AIService, AIServiceError
|
||||
from backend.app.models.workout import Workout
|
||||
import json
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from app.services.garmin import GarminService
|
||||
from app.models.garmin_sync_log import GarminSyncStatus
|
||||
from backend.app.services.garmin import GarminService
|
||||
from backend.app.models.garmin_sync_log import GarminSyncStatus
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from app.services.plan_evolution import PlanEvolutionService
|
||||
from app.models.plan import Plan
|
||||
from app.models.analysis import Analysis
|
||||
from backend.app.services.plan_evolution import PlanEvolutionService
|
||||
from backend.app.models.plan import Plan
|
||||
from backend.app.models.analysis import Analysis
|
||||
from datetime import datetime
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from app.services.workout_sync import WorkoutSyncService
|
||||
from app.models.workout import Workout
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from backend.app.services.workout_sync import WorkoutSyncService
|
||||
from backend.app.models.workout import Workout
|
||||
from backend.app.models.garmin_sync_log import GarminSyncLog
|
||||
from datetime import datetime, timedelta
|
||||
import asyncio
|
||||
|
||||
|
||||
Reference in New Issue
Block a user