Initial commit: Sproutly plant tracking app

This commit is contained in:
2026-03-08 23:27:16 -07:00
commit 4f66102bbb
20 changed files with 2643 additions and 0 deletions

86
backend/main.py Normal file
View File

@@ -0,0 +1,86 @@
import asyncio
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from database import SessionLocal
from models import Settings, NotificationLog
from routers import varieties, batches, dashboard, settings, notifications
from routers.notifications import build_daily_summary, send_ntfy
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("sproutly")
scheduler = AsyncIOScheduler()
async def scheduled_daily_notification():
db = SessionLocal()
try:
s = db.query(Settings).filter(Settings.id == 1).first()
if not s or not s.ntfy_topic:
logger.info("Daily notification skipped: ntfy not configured")
return
summary = build_daily_summary(db)
ok, detail = await send_ntfy(s, "Sproutly Daily Summary", summary, db)
logger.info(f"Daily notification: {detail}")
except Exception as e:
logger.error(f"Daily notification error: {e}")
log = NotificationLog(message="scheduler error", status="failed", error=str(e))
db.add(log)
db.commit()
finally:
db.close()
def get_notification_schedule(db) -> tuple[int, int]:
try:
s = db.query(Settings).filter(Settings.id == 1).first()
if s and s.notification_time:
h, m = s.notification_time.split(":")
return int(h), int(m)
except Exception:
pass
return 7, 0
@asynccontextmanager
async def lifespan(app: FastAPI):
db = SessionLocal()
hour, minute = get_notification_schedule(db)
db.close()
scheduler.add_job(
scheduled_daily_notification,
CronTrigger(hour=hour, minute=minute),
id="daily_summary",
replace_existing=True,
)
scheduler.start()
logger.info(f"Scheduler started — daily notification at {hour:02d}:{minute:02d}")
yield
scheduler.shutdown()
app = FastAPI(title="Sproutly API", version="1.0.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(varieties.router)
app.include_router(batches.router)
app.include_router(dashboard.router)
app.include_router(settings.router)
app.include_router(notifications.router)
@app.get("/health")
def health():
return {"status": "ok", "service": "sproutly"}