add homekeeper stack
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Any
|
||||
|
||||
from app.database import get_session
|
||||
from app.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _row(r) -> dict:
|
||||
return dict(r._mapping)
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_colonies(
|
||||
apiary_id: int | None = Query(default=None),
|
||||
include_closed: bool = Query(default=False),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
where = "1=1" if include_closed else "c.closed_at IS NULL"
|
||||
params: dict[str, Any] = {}
|
||||
if apiary_id is not None:
|
||||
where += " AND c.apiary_id = :apiary_id"
|
||||
params["apiary_id"] = apiary_id
|
||||
|
||||
rows = session.execute(text(f"""
|
||||
SELECT
|
||||
c.*,
|
||||
a.name AS apiary_name,
|
||||
p.name AS parent_name,
|
||||
q.year AS queen_year,
|
||||
q.breed AS queen_breed,
|
||||
(SELECT MAX(i.date::DATE)
|
||||
FROM inspections i
|
||||
WHERE i.colony_id = c.id) AS last_inspection_date
|
||||
FROM colonies c
|
||||
LEFT JOIN apiaries a ON a.id = c.apiary_id
|
||||
LEFT JOIN colonies p ON p.id = c.parent_colony_id
|
||||
LEFT JOIN queens q ON q.id = c.current_queen_id
|
||||
WHERE {where}
|
||||
ORDER BY a.name, c.name
|
||||
"""), params).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/log-placeholder", include_in_schema=False)
|
||||
def _log_placeholder():
|
||||
pass # just to force ordering; actual log route is below
|
||||
|
||||
|
||||
@router.get("/{id}/log")
|
||||
def colony_log(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT date, type, summary, ref_id, created_at FROM (
|
||||
SELECT date, 'durchsicht' AS type,
|
||||
TRIM(BOTH FROM CONCAT_WS(' ',
|
||||
CASE WHEN brood_frames IS NOT NULL THEN brood_frames::TEXT || ' BW' END,
|
||||
CASE WHEN honey_frames IS NOT NULL THEN honey_frames::TEXT || ' HW' END,
|
||||
CASE WHEN colony_strength IS NOT NULL THEN 'Stärke ' || colony_strength::TEXT END,
|
||||
CASE WHEN queen_seen = 1 THEN '♛ gesehen' END
|
||||
)) AS summary,
|
||||
id AS ref_id, created_at
|
||||
FROM inspections WHERE colony_id = :colony_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT date, 'kontrolle' AS type,
|
||||
count::TEXT || ' Milben' ||
|
||||
CASE WHEN date_from IS NOT NULL
|
||||
THEN ' · ' || (date::DATE - date_from::DATE)::TEXT || ' Tage' ELSE '' END AS summary,
|
||||
id AS ref_id, created_at
|
||||
FROM varroa_counts WHERE colony_id = :colony_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT date, 'behandlung' AS type,
|
||||
product || CASE WHEN dosage IS NOT NULL AND dosage <> ''
|
||||
THEN ' · ' || dosage ELSE '' END AS summary,
|
||||
id AS ref_id, created_at
|
||||
FROM varroa_treatments WHERE colony_id = :colony_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT vc.date, 'nachkontrolle' AS type,
|
||||
vt.product || CASE WHEN vc.result IS NOT NULL AND vc.result <> ''
|
||||
THEN ': ' || vc.result ELSE '' END AS summary,
|
||||
vc.id AS ref_id, vc.created_at
|
||||
FROM varroa_controls vc
|
||||
JOIN varroa_treatments vt ON vt.id = vc.treatment_id
|
||||
WHERE vc.colony_id = :colony_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT h.date, 'ernte' AS type,
|
||||
h.weight_kg::TEXT || ' kg' ||
|
||||
CASE WHEN h.type IS NOT NULL THEN ' · ' || h.type ELSE '' END AS summary,
|
||||
h.id AS ref_id, h.created_at
|
||||
FROM honey_harvests h WHERE h.colony_id = :colony_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT ca.date, 'massnahme' AS type,
|
||||
CASE ca.type
|
||||
WHEN 'ablegerbildung' THEN 'Ablegerbildung'
|
||||
WHEN 'zwischenbodenableger' THEN 'Zwischenbödenableger'
|
||||
WHEN 'brutentnahme' THEN 'Brutentnahme'
|
||||
WHEN 'standwechsel' THEN 'Standwechsel'
|
||||
WHEN 'futterung' THEN 'Fütterung'
|
||||
WHEN 'vereinigung' THEN 'Völkervereinigung'
|
||||
ELSE 'Maßnahme'
|
||||
END ||
|
||||
CASE WHEN ca.notes IS NOT NULL AND ca.notes <> ''
|
||||
THEN ': ' || ca.notes ELSE '' END AS summary,
|
||||
ca.id AS ref_id, ca.created_at
|
||||
FROM colony_actions ca WHERE ca.colony_id = :colony_id
|
||||
) log
|
||||
ORDER BY date::DATE DESC, created_at DESC
|
||||
"""), {"colony_id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}/lineage")
|
||||
def colony_lineage(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
WITH RECURSIVE lineage(id, name, status, parent_colony_id, depth) AS (
|
||||
SELECT id, name, status, parent_colony_id, 0
|
||||
FROM colonies WHERE id = :colony_id
|
||||
UNION ALL
|
||||
SELECT c.id, c.name, c.status, c.parent_colony_id, l.depth + 1
|
||||
FROM colonies c
|
||||
JOIN lineage l ON c.id = l.parent_colony_id
|
||||
)
|
||||
SELECT * FROM lineage ORDER BY depth DESC
|
||||
"""), {"colony_id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}/descendants")
|
||||
def colony_descendants(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
WITH RECURSIVE descendants(id, name, status, parent_colony_id, depth) AS (
|
||||
SELECT id, name, status, parent_colony_id, 0
|
||||
FROM colonies WHERE id = :colony_id
|
||||
UNION ALL
|
||||
SELECT c.id, c.name, c.status, c.parent_colony_id, d.depth + 1
|
||||
FROM colonies c
|
||||
JOIN descendants d ON c.parent_colony_id = d.id
|
||||
)
|
||||
SELECT * FROM descendants WHERE depth > 0 ORDER BY depth, name
|
||||
"""), {"colony_id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}/location-history")
|
||||
def colony_location_history(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT cl.*, a.name AS apiary_name
|
||||
FROM colony_locations cl
|
||||
JOIN apiaries a ON a.id = cl.apiary_id
|
||||
WHERE cl.colony_id = :colony_id
|
||||
ORDER BY cl.moved_at DESC
|
||||
"""), {"colony_id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_colony(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
SELECT c.*, a.name AS apiary_name, p.name AS parent_name
|
||||
FROM colonies c
|
||||
LEFT JOIN apiaries a ON a.id = c.apiary_id
|
||||
LEFT JOIN colonies p ON p.id = c.parent_colony_id
|
||||
WHERE c.id = :id
|
||||
"""), {"id": id}).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_colony(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO colonies (name, apiary_id, status, parent_colony_id, notes)
|
||||
VALUES (:name, :apiary_id, :status, :parent_colony_id, :notes)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"name": body.get("name"),
|
||||
"apiary_id": body.get("apiary_id"),
|
||||
"status": body.get("status", "wirtschaftsvolk"),
|
||||
"parent_colony_id": body.get("parent_colony_id"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
colony_id = row._mapping["id"]
|
||||
session.execute(text("""
|
||||
INSERT INTO colony_locations (colony_id, apiary_id) VALUES (:colony_id, :apiary_id)
|
||||
"""), {"colony_id": colony_id, "apiary_id": body.get("apiary_id")})
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_colony(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
# Check if apiary changed (to insert colony_location)
|
||||
old = session.execute(
|
||||
text("SELECT apiary_id FROM colonies WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
|
||||
result = session.execute(text("""
|
||||
UPDATE colonies
|
||||
SET name = :name, apiary_id = :apiary_id, status = :status, notes = :notes
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"name": body.get("name"),
|
||||
"apiary_id": body.get("apiary_id"),
|
||||
"status": body.get("status"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
|
||||
new_apiary_id = body.get("apiary_id")
|
||||
if old is not None and old._mapping["apiary_id"] != new_apiary_id:
|
||||
session.execute(text("""
|
||||
INSERT INTO colony_locations (colony_id, apiary_id) VALUES (:colony_id, :apiary_id)
|
||||
"""), {"colony_id": id, "apiary_id": new_apiary_id})
|
||||
|
||||
session.commit()
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.post("/{id}/close")
|
||||
def close_colony(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE colonies SET status = 'eingegangen', closed_at = NOW()
|
||||
WHERE id = :id RETURNING *
|
||||
"""), {"id": id}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.post("/{id}/set-queen")
|
||||
def set_queen(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE colonies SET current_queen_id = :queen_id WHERE id = :id RETURNING *
|
||||
"""), {"id": id, "queen_id": body.get("queen_id")}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
Reference in New Issue
Block a user